#!/usr/bin/python # Copyright: (c) 2019, Hetzner Cloud GmbH # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import annotations DOCUMENTATION = """ --- module: server short_description: Create and manage cloud servers on the Hetzner Cloud. description: - Create, update and manage cloud servers on the Hetzner Cloud. author: - Lukas Kaemmerling (@LKaemmerling) options: id: description: - ID of the Hetzner Cloud Server to manage. - Only required if no server O(name) is given type: int name: description: - Name of the Hetzner Cloud Server to manage. - Only required if no server O(id) is given or a server does not exist. type: str server_type: description: - Hetzner Cloud Server Type (name or ID) of the server. - Required if server does not exist. type: str ssh_keys: description: - List of Hetzner Cloud SSH Keys (name or ID) to create the server with. - Only used during the server creation. type: list elements: str volumes: description: - List of Hetzner Cloud Volumes (name or ID) that should be attached to the server. - Only used during the server creation. type: list elements: str firewalls: description: - List of Hetzner Cloud Firewalls (name or ID) that should be attached to the server. type: list elements: str image: description: - Hetzner Cloud Image (name or ID) to create the server from. - Required if server does not exist or when O(state=rebuild). type: str image_allow_deprecated: description: - Allows the creation of servers with deprecated images. type: bool default: false aliases: [allow_deprecated_image] location: description: - Hetzner Cloud Location (name or ID) to create the server in. - Required if no O(datacenter) is given and server does not exist. - Only used during the server creation. type: str datacenter: description: - Hetzner Cloud Datacenter (name or ID) to create the server in. - Required if no O(location) is given and server does not exist. - Only used during the server creation. type: str backups: description: - Enable or disable Backups for the given Server. type: bool upgrade_disk: description: - Resize the disk size, when resizing a server. - If you want to downgrade the server later, this value should be False. type: bool default: false enable_ipv4: description: - Enables the public ipv4 address. type: bool default: true enable_ipv6: description: - Enables the public ipv6 address. type: bool default: true ipv4: description: - Hetzner Cloud Primary IPv4 (name or ID) to use. - If omitted and O(enable_ipv4=true), a new ipv4 Primary IP will automatically be created. type: str ipv6: description: - Hetzner Cloud Primary IPv6 (name or ID) to use. - If omitted and O(enable_ipv6=true), a new ipv6 Primary IP will automatically be created. type: str private_networks: description: - List of Hetzner Cloud Networks (name or ID) the server should be attached to. - If None, private networks are left as they are (e.g. if previously added by hcloud_server_network), if it has any other value (including []), only those networks are attached to the server. type: list elements: str force: description: - Force the update of the server. - May power off the server if update is applied. type: bool default: false aliases: [force_upgrade] user_data: description: - User Data to be passed to the server on creation. - Only used during the server creation. type: str rescue_mode: description: - Add the Hetzner rescue system type you want the server to be booted into. type: str labels: description: - User-defined labels (key-value pairs). type: dict delete_protection: description: - Protect the Server for deletion. - Needs to be the same as O(rebuild_protection). type: bool rebuild_protection: description: - Protect the Server for rebuild. - Needs to be the same as O(delete_protection). type: bool placement_group: description: - Hetzner Cloud Placement Group (name or ID) to create the server in. type: str state: description: - State of the server. default: present choices: [ absent, present, restarted, started, stopped, rebuild ] type: str extends_documentation_fragment: - hetzner.hcloud.hcloud """ EXAMPLES = """ - name: Create a basic server hetzner.hcloud.server: name: my-server server_type: cx11 image: ubuntu-22.04 state: present - name: Create a basic server with ssh key hetzner.hcloud.server: name: my-server server_type: cx11 image: ubuntu-22.04 location: fsn1 ssh_keys: - me@myorganisation state: present - name: Resize an existing server hetzner.hcloud.server: name: my-server server_type: cx21 upgrade_disk: true state: present - name: Ensure the server is absent (remove if needed) hetzner.hcloud.server: name: my-server state: absent - name: Ensure the server is started hetzner.hcloud.server: name: my-server state: started - name: Ensure the server is stopped hetzner.hcloud.server: name: my-server state: stopped - name: Ensure the server is restarted hetzner.hcloud.server: name: my-server state: restarted - name: Ensure the server is will be booted in rescue mode and therefore restarted hetzner.hcloud.server: name: my-server rescue_mode: linux64 state: restarted - name: Ensure the server is rebuild hetzner.hcloud.server: name: my-server image: ubuntu-22.04 state: rebuild - name: Add server to placement group hetzner.hcloud.server: name: my-server placement_group: my-placement-group force: true state: present - name: Remove server from placement group hetzner.hcloud.server: name: my-server placement_group: state: present - name: Add server with private network only hetzner.hcloud.server: name: my-server enable_ipv4: false enable_ipv6: false private_networks: - my-network - 4711 state: present """ RETURN = """ hcloud_server: description: The server instance returned: Always type: complex contains: id: description: Numeric identifier of the server returned: always type: int sample: 1937415 name: description: Name of the server returned: always type: str sample: my-server created: description: Point in time when the Server was created (in ISO-8601 format) returned: always type: str sample: "2023-11-06T13:36:56+00:00" status: description: Status of the server returned: always type: str sample: running server_type: description: Name of the server type of the server returned: always type: str sample: cx11 ipv4_address: description: Public IPv4 address of the server returned: always type: str sample: 116.203.104.109 ipv6: description: IPv6 network of the server returned: always type: str sample: 2a01:4f8:1c1c:c140::/64 private_networks: description: List of private networks the server is attached to (name or ID) returned: always type: list elements: str sample: ['my-network', 'another-network', '4711'] private_networks_info: description: List of private networks the server is attached to (dict with name and ip) returned: always type: list elements: dict sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] location: description: Name of the location of the server returned: always type: str sample: fsn1 placement_group: description: Placement Group of the server type: str returned: always sample: 4711 version_added: "1.5.0" datacenter: description: Name of the datacenter of the server returned: always type: str sample: fsn1-dc14 rescue_enabled: description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot returned: always type: bool sample: false backup_window: description: Time window (UTC) in which the backup will run, or null if the backups are not enabled returned: always type: bool sample: 22-02 labels: description: User-defined labels (key-value pairs) returned: always type: dict delete_protection: description: True if server is protected for deletion type: bool returned: always sample: false version_added: "0.1.0" rebuild_protection: description: True if server is protected for rebuild type: bool returned: always sample: false version_added: "0.1.0" """ from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Literal from ansible.module_utils.basic import AnsibleModule from ..module_utils.hcloud import AnsibleHCloud from ..module_utils.vendor.hcloud import HCloudException from ..module_utils.vendor.hcloud.firewalls import FirewallResource from ..module_utils.vendor.hcloud.servers import ( BoundServer, Server, ServerCreatePublicNetwork, ) if TYPE_CHECKING: from ..module_utils.vendor.hcloud.actions import BoundAction from ..module_utils.vendor.hcloud.firewalls import BoundFirewall from ..module_utils.vendor.hcloud.networks import BoundNetwork from ..module_utils.vendor.hcloud.placement_groups import BoundPlacementGroup from ..module_utils.vendor.hcloud.primary_ips import PrimaryIP from ..module_utils.vendor.hcloud.server_types import ServerType class AnsibleHCloudServer(AnsibleHCloud): represent = "hcloud_server" hcloud_server: BoundServer | None = None def _prepare_result(self): return { "id": str(self.hcloud_server.id), "name": self.hcloud_server.name, "created": self.hcloud_server.created.isoformat(), "ipv4_address": ( self.hcloud_server.public_net.ipv4.ip if self.hcloud_server.public_net.ipv4 is not None else None ), "ipv6": self.hcloud_server.public_net.ipv6.ip if self.hcloud_server.public_net.ipv6 is not None else None, "private_networks": [net.network.name for net in self.hcloud_server.private_net], "private_networks_info": [ {"name": net.network.name, "ip": net.ip} for net in self.hcloud_server.private_net ], "image": self.hcloud_server.image.name if self.hcloud_server.image is not None else None, "server_type": self.hcloud_server.server_type.name, "datacenter": self.hcloud_server.datacenter.name, "location": self.hcloud_server.datacenter.location.name, "placement_group": ( self.hcloud_server.placement_group.name if self.hcloud_server.placement_group is not None else None ), "rescue_enabled": self.hcloud_server.rescue_enabled, "backup_window": self.hcloud_server.backup_window, "labels": self.hcloud_server.labels, "delete_protection": self.hcloud_server.protection["delete"], "rebuild_protection": self.hcloud_server.protection["rebuild"], "status": self.hcloud_server.status, } def _get_server(self): try: if self.module.params.get("id") is not None: self.hcloud_server = self.client.servers.get_by_id(self.module.params.get("id")) else: self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("name")) except HCloudException as exception: self.fail_json_hcloud(exception) def _create_server(self): self.module.fail_on_missing_params(required_params=["name", "server_type", "image"]) server_type = self._get_server_type() image = self._get_image(server_type) params = { "name": self.module.params.get("name"), "labels": self.module.params.get("labels"), "server_type": server_type, "image": image, "user_data": self.module.params.get("user_data"), "public_net": ServerCreatePublicNetwork( enable_ipv4=self.module.params.get("enable_ipv4"), enable_ipv6=self.module.params.get("enable_ipv6"), ), } if self.module.params.get("placement_group") is not None: params["placement_group"] = self._client_get_by_name_or_id( "placement_groups", self.module.params.get("placement_group") ) if self.module.params.get("ipv4") is not None: params["public_net"].ipv4 = self._client_get_by_name_or_id("primary_ips", self.module.params.get("ipv4")) if self.module.params.get("ipv6") is not None: params["public_net"].ipv6 = self._client_get_by_name_or_id("primary_ips", self.module.params.get("ipv6")) if self.module.params.get("private_networks") is not None: params["networks"] = [ self._client_get_by_name_or_id("networks", name_or_id) for name_or_id in self.module.params.get("private_networks") ] if self.module.params.get("ssh_keys") is not None: params["ssh_keys"] = [ self._client_get_by_name_or_id("ssh_keys", name_or_id) for name_or_id in self.module.params.get("ssh_keys") ] if self.module.params.get("volumes") is not None: params["volumes"] = [ self._client_get_by_name_or_id("volumes", name_or_id) for name_or_id in self.module.params.get("volumes") ] if self.module.params.get("firewalls") is not None: params["firewalls"] = [ self._client_get_by_name_or_id("firewalls", name_or_id) for name_or_id in self.module.params.get("firewalls") ] if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: # When not given, the API will choose the location. params["location"] = None params["datacenter"] = None elif self.module.params.get("location") is not None and self.module.params.get("datacenter") is None: params["location"] = self._client_get_by_name_or_id("locations", self.module.params.get("location")) elif self.module.params.get("location") is None and self.module.params.get("datacenter") is not None: params["datacenter"] = self._client_get_by_name_or_id("datacenters", self.module.params.get("datacenter")) if self.module.params.get("state") == "stopped": params["start_after_create"] = False if not self.module.check_mode: try: resp = self.client.servers.create(**params) self.result["root_password"] = resp.root_password # Action should take 60 to 90 seconds on average, but can be >10m when creating a # server from a custom images resp.action.wait_until_finished(max_retries=1800) for action in resp.next_actions: action.wait_until_finished() rescue_mode = self.module.params.get("rescue_mode") if rescue_mode: self._get_server() self._set_rescue_mode(rescue_mode) backups = self.module.params.get("backups") if backups: self._get_server() action = self.hcloud_server.enable_backup() action.wait_until_finished() delete_protection = self.module.params.get("delete_protection") rebuild_protection = self.module.params.get("rebuild_protection") if delete_protection is not None and rebuild_protection is not None: self._get_server() action = self.hcloud_server.change_protection( delete=delete_protection, rebuild=rebuild_protection, ) action.wait_until_finished() except HCloudException as exception: self.fail_json_hcloud(exception) self._mark_as_changed() self._get_server() def _get_image(self, server_type: ServerType): image = self.client.images.get_by_name_and_architecture( name=self.module.params.get("image"), architecture=server_type.architecture, include_deprecated=True, ) if image is None: image = self.client.images.get_by_id(self.module.params.get("image")) if image.deprecated is not None: available_until = image.deprecated + timedelta(days=90) if self.module.params.get("image_allow_deprecated"): self.module.warn( f"You try to use a deprecated image. The image {image.name} will " f"continue to be available until {available_until.strftime('%Y-%m-%d')}." ) else: self.module.fail_json( msg=( f"You try to use a deprecated image. The image {image.name} will " f"continue to be available until {available_until.strftime('%Y-%m-%d')}. " "If you want to use this image use image_allow_deprecated=true." ) ) return image def _get_server_type(self) -> ServerType: server_type = self._client_get_by_name_or_id("server_types", self.module.params.get("server_type")) self._check_and_warn_deprecated_server(server_type) return server_type def _check_and_warn_deprecated_server(self, server_type: ServerType) -> None: if server_type.deprecation is None: return if server_type.deprecation.unavailable_after < datetime.now(timezone.utc): self.module.warn( f"Attention: The server plan {server_type.name} is deprecated and can " "no longer be ordered. Existing servers of that plan will continue to " "work as before and no action is required on your part. " "It is possible to migrate this server to another server plan by setting " "the server_type parameter on the hetzner.hcloud.server module." ) else: server_type_unavailable_date = server_type.deprecation.unavailable_after.strftime("%Y-%m-%d") self.module.warn( f"Attention: The server plan {server_type.name} is deprecated and will " f"no longer be available for order as of {server_type_unavailable_date}. " "Existing servers of that plan will continue to work as before and no " "action is required on your part. " "It is possible to migrate this server to another server plan by setting " "the server_type parameter on the hetzner.hcloud.server module." ) def _update_server(self) -> None: try: previous_server_status = self.hcloud_server.status labels = self.module.params.get("labels") if labels is not None and labels != self.hcloud_server.labels: if not self.module.check_mode: self.hcloud_server.update(labels=labels) self._mark_as_changed() rescue_mode = self.module.params.get("rescue_mode") if rescue_mode and self.hcloud_server.rescue_enabled is False: if not self.module.check_mode: self._set_rescue_mode(rescue_mode) self._mark_as_changed() elif not rescue_mode and self.hcloud_server.rescue_enabled is True: if not self.module.check_mode: action = self.hcloud_server.disable_rescue() action.wait_until_finished() self._mark_as_changed() backups = self.module.params.get("backups") if backups and self.hcloud_server.backup_window is None: if not self.module.check_mode: action = self.hcloud_server.enable_backup() action.wait_until_finished() self._mark_as_changed() elif backups is not None and not backups and self.hcloud_server.backup_window is not None: if not self.module.check_mode: action = self.hcloud_server.disable_backup() action.wait_until_finished() self._mark_as_changed() if self.module.params.get("firewalls") is not None: self._update_server_firewalls() if self.module.params.get("placement_group") is not None: self._update_server_placement_group() if self.module.params.get("ipv4") is not None: self._update_server_ip("ipv4") if self.module.params.get("ipv6") is not None: self._update_server_ip("ipv6") if self.module.params.get("private_networks") is not None: self._update_server_networks() if self.module.params.get("server_type") is not None: self._update_server_server_type() if not self.module.check_mode and ( (self.module.params.get("state") == "present" and previous_server_status == Server.STATUS_RUNNING) or self.module.params.get("state") == "started" ): self.start_server() delete_protection = self.module.params.get("delete_protection") rebuild_protection = self.module.params.get("rebuild_protection") if (delete_protection is not None and rebuild_protection is not None) and ( delete_protection != self.hcloud_server.protection["delete"] or rebuild_protection != self.hcloud_server.protection["rebuild"] ): if not self.module.check_mode: action = self.hcloud_server.change_protection( delete=delete_protection, rebuild=rebuild_protection, ) action.wait_until_finished() self._mark_as_changed() self._get_server() except HCloudException as exception: self.fail_json_hcloud(exception) def _update_server_placement_group(self) -> None: current: BoundPlacementGroup | None = self.hcloud_server.placement_group wanted = self.module.params.get("placement_group") # Return if nothing changed if current is not None and current.has_id_or_name(wanted): return # Fetch resource if parameter is truthy if wanted: placement_group = self._client_get_by_name_or_id("placement_groups", wanted) # Remove if current is defined if current is not None: if not self.module.check_mode: action = self.hcloud_server.remove_from_placement_group() action.wait_until_finished() self._mark_as_changed() # Return if parameter is falsy if not wanted: return # Assign new self.stop_server_if_forced() if not self.module.check_mode: action = self.hcloud_server.add_to_placement_group(placement_group) action.wait_until_finished() self._mark_as_changed() def _update_server_server_type(self) -> None: current: ServerType = self.hcloud_server.server_type wanted = self.module.params.get("server_type") # Return if nothing changed if current.has_id_or_name(wanted): # Check if we should warn for using an deprecated server type self._check_and_warn_deprecated_server(self.hcloud_server.server_type) return self.stop_server_if_forced() upgrade_disk = self.module.params.get("upgrade_disk") # Upgrading a server takes 160 seconds on average, upgrading the disk should # take more time upgrade_timeout = 600 if upgrade_disk else 180 if not self.module.check_mode: action = self.hcloud_server.change_type( server_type=self._get_server_type(), upgrade_disk=upgrade_disk, ) action.wait_until_finished(max_retries=upgrade_timeout) self._mark_as_changed() def _update_server_ip(self, kind: Literal["ipv4", "ipv6"]) -> None: current: PrimaryIP | None = getattr(self.hcloud_server.public_net, f"primary_{kind}") wanted = self.module.params.get(kind) enable = self.module.params.get(f"enable_{kind}") # Return if nothing changed if current is not None and current.has_id_or_name(wanted) and enable: return # Fetch resource if parameter is truthy if wanted: primary_ip = self._client_get_by_name_or_id("primary_ips", wanted) # Remove if current is defined if current is not None: self.stop_server_if_forced() if not self.module.check_mode: action = self.client.primary_ips.unassign(current) action.wait_until_finished() self._mark_as_changed() # Return if parameter is falsy or resource is disabled if not wanted or not enable: return # Assign new self.stop_server_if_forced() if not self.module.check_mode: action = self.client.primary_ips.assign( primary_ip, assignee_id=self.hcloud_server.id, assignee_type="server", ) action.wait_until_finished() self._mark_as_changed() def _update_server_networks(self) -> None: current: list[BoundNetwork] = [item.network for item in self.hcloud_server.private_net] wanted: list[BoundNetwork] = [ self._client_get_by_name_or_id("networks", name_or_id) for name_or_id in self.module.params.get("private_networks") ] current_ids = {item.id for item in current} wanted_ids = {item.id for item in wanted} # Removing existing but not wanted networks actions: list[BoundAction] = [] for current_network in current: if current_network.id in wanted_ids: continue self._mark_as_changed() if self.module.check_mode: continue actions.append(self.hcloud_server.detach_from_network(current_network)) for action in actions: action.wait_until_finished() # Adding wanted networks that doesn't exist yet actions: list[BoundAction] = [] for wanted_network in wanted: if wanted_network.id in current_ids: continue self._mark_as_changed() if self.module.check_mode: continue actions.append(self.hcloud_server.attach_to_network(wanted_network)) for action in actions: action.wait_until_finished() def _update_server_firewalls(self) -> None: current: list[BoundFirewall] = [item.firewall for item in self.hcloud_server.public_net.firewalls] wanted: list[BoundFirewall] = [ self._client_get_by_name_or_id("firewalls", name_or_id) for name_or_id in self.module.params.get("firewalls") ] current_ids = {item.id for item in current} wanted_ids = {item.id for item in wanted} # Removing existing but not wanted firewalls actions: list[BoundAction] = [] for current_firewall in current: if current_firewall.id in wanted_ids: continue self._mark_as_changed() if self.module.check_mode: continue actions.extend( self.client.firewalls.remove_from_resources( current_firewall, [FirewallResource(type="server", server=self.hcloud_server)], ) ) for action in actions: action.wait_until_finished() # Adding wanted firewalls that doesn't exist yet actions: list[BoundAction] = [] for wanted_firewall in wanted: if wanted_firewall.id in current_ids: continue self._mark_as_changed() if self.module.check_mode: continue actions.extend( self.client.firewalls.apply_to_resources( wanted_firewall, [FirewallResource(type="server", server=self.hcloud_server)], ) ) for action in actions: action.wait_until_finished() def _set_rescue_mode(self, rescue_mode): if self.module.params.get("ssh_keys"): resp = self.hcloud_server.enable_rescue( type=rescue_mode, ssh_keys=[ self.client.ssh_keys.get_by_name(ssh_key_name).id for ssh_key_name in self.module.params.get("ssh_keys") ], ) else: resp = self.hcloud_server.enable_rescue(type=rescue_mode) resp.action.wait_until_finished() self.result["root_password"] = resp.root_password def start_server(self): try: if self.hcloud_server: if self.hcloud_server.status != Server.STATUS_RUNNING: if not self.module.check_mode: action = self.client.servers.power_on(self.hcloud_server) action.wait_until_finished() self._mark_as_changed() self._get_server() except HCloudException as exception: self.fail_json_hcloud(exception) def stop_server(self): try: if self.hcloud_server: if self.hcloud_server.status != Server.STATUS_OFF: if not self.module.check_mode: action = self.client.servers.power_off(self.hcloud_server) action.wait_until_finished() self._mark_as_changed() self._get_server() except HCloudException as exception: self.fail_json_hcloud(exception) def stop_server_if_forced(self): previous_server_status = self.hcloud_server.status if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode: if self.module.params.get("force") or self.module.params.get("state") == "stopped": self.stop_server() # Only stopped server can be upgraded return previous_server_status self.module.warn( f"You can not upgrade a running instance {self.hcloud_server.name}. " "You need to stop the instance or use force=true." ) return None def rebuild_server(self): self.module.fail_on_missing_params(required_params=["image"]) try: if not self.module.check_mode: image = self._get_image(self.hcloud_server.server_type) # When we rebuild the server progress takes some more time. resp = self.client.servers.rebuild(self.hcloud_server, image, return_response=True) resp.action.wait_until_finished(1000) self._mark_as_changed() self._get_server() except HCloudException as exception: self.fail_json_hcloud(exception) def present_server(self): self._get_server() if self.hcloud_server is None: self._create_server() else: self._update_server() def delete_server(self): try: self._get_server() if self.hcloud_server is not None: if not self.module.check_mode: action = self.client.servers.delete(self.hcloud_server) action.wait_until_finished() self._mark_as_changed() self.hcloud_server = None except HCloudException as exception: self.fail_json_hcloud(exception) @classmethod def define_module(cls): return AnsibleModule( argument_spec=dict( id={"type": "int"}, name={"type": "str"}, image={"type": "str"}, image_allow_deprecated={"type": "bool", "default": False, "aliases": ["allow_deprecated_image"]}, server_type={"type": "str"}, location={"type": "str"}, datacenter={"type": "str"}, user_data={"type": "str"}, ssh_keys={"type": "list", "elements": "str", "no_log": False}, volumes={"type": "list", "elements": "str"}, firewalls={"type": "list", "elements": "str"}, labels={"type": "dict"}, backups={"type": "bool"}, upgrade_disk={"type": "bool", "default": False}, enable_ipv4={"type": "bool", "default": True}, enable_ipv6={"type": "bool", "default": True}, ipv4={"type": "str"}, ipv6={"type": "str"}, private_networks={"type": "list", "elements": "str", "default": None}, force={ "type": "bool", "default": False, "aliases": ["force_upgrade"], "deprecated_aliases": [ {"collection_name": "hetzner.hcloud", "name": "force_upgrade", "version": "4.0.0"} ], }, rescue_mode={"type": "str"}, delete_protection={"type": "bool"}, rebuild_protection={"type": "bool"}, placement_group={"type": "str"}, state={ "choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"], "default": "present", }, **super().base_module_arguments(), ), required_one_of=[["id", "name"]], mutually_exclusive=[["location", "datacenter"]], required_together=[["delete_protection", "rebuild_protection"]], supports_check_mode=True, ) def main(): module = AnsibleHCloudServer.define_module() hcloud = AnsibleHCloudServer(module) state = module.params.get("state") if state == "absent": hcloud.delete_server() elif state == "present": hcloud.present_server() elif state == "started": hcloud.present_server() hcloud.start_server() elif state == "stopped": hcloud.present_server() hcloud.stop_server() elif state == "restarted": hcloud.present_server() hcloud.stop_server() hcloud.start_server() elif state == "rebuild": hcloud.present_server() hcloud.rebuild_server() module.exit_json(**hcloud.get_result()) if __name__ == "__main__": main()