summaryrefslogtreecommitdiffstats
path: root/ansible_collections/hetzner/hcloud/plugins/modules/server.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-05 16:18:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-05 16:18:34 +0000
commit3667197efb7b18ec842efd504785965911f8ac4b (patch)
tree0b986a4bc6879d080b100666a97cdabbc9ca1f28 /ansible_collections/hetzner/hcloud/plugins/modules/server.py
parentAdding upstream version 9.5.1+dfsg. (diff)
downloadansible-3667197efb7b18ec842efd504785965911f8ac4b.tar.xz
ansible-3667197efb7b18ec842efd504785965911f8ac4b.zip
Adding upstream version 10.0.0+dfsg.upstream/10.0.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/hetzner/hcloud/plugins/modules/server.py')
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/server.py675
1 files changed, 348 insertions, 327 deletions
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/server.py b/ansible_collections/hetzner/hcloud/plugins/modules/server.py
index f5cadb807..d7bae3fc1 100644
--- a/ansible_collections/hetzner/hcloud/plugins/modules/server.py
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/server.py
@@ -22,50 +22,58 @@ author:
options:
id:
description:
- - The ID of the Hetzner Cloud server to manage.
- - Only required if no server I(name) is given
+ - ID of the Hetzner Cloud Server to manage.
+ - Only required if no server O(name) is given
type: int
name:
description:
- - The Name of the Hetzner Cloud server to manage.
- - Only required if no server I(id) is given or a server does not exist.
+ - 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:
- - The Server Type of the Hetzner Cloud server to manage.
+ - Hetzner Cloud Server Type (name or ID) of the server.
- Required if server does not exist.
type: str
ssh_keys:
description:
- - List of SSH key names
- - The key names correspond to the SSH keys configured for your
- Hetzner Cloud account access.
+ - 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 Volumes IDs that should be attached to the server on server creation.
+ - 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 Firewall IDs that should be attached to the server on server creation.
+ - List of Hetzner Cloud Firewalls (name or ID) that should be attached to the server.
type: list
elements: str
image:
description:
- - Image the server should be created from.
- - Required if server does not exist.
+ - 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:
- - Location of Server.
- - Required if no I(datacenter) is given and server does not exist.
+ - 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:
- - Datacenter of Server.
- - Required if no I(location) is given and server does not exist.
+ - 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:
@@ -79,50 +87,42 @@ options:
default: false
enable_ipv4:
description:
- - Enables the public ipv4 address
+ - Enables the public ipv4 address.
type: bool
default: true
enable_ipv6:
description:
- - Enables the public ipv6 address
+ - Enables the public ipv6 address.
type: bool
default: true
ipv4:
description:
- - ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a new ipv4 Primary IP will automatically be created
+ - 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:
- - ID of the ipv6 Primary IP to use. If omitted and enable_ipv6 is true, a new ipv6 Primary IP will automatically be created.
+ - 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 private networks the server is attached to (name or ID)
+ - 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_upgrade:
- description:
- - Deprecated
- - Force the upgrade of the server.
- - Power off the server if it is running on upgrade.
- type: bool
force:
description:
- Force the update of the server.
- - May power off the server if update.
- type: bool
- default: false
- allow_deprecated_image:
- description:
- - Allows the creation of servers with deprecated images.
+ - 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 if server does not exist.
+ - Only used during the server creation.
type: str
rescue_mode:
description:
@@ -135,16 +135,16 @@ options:
delete_protection:
description:
- Protect the Server for deletion.
- - Needs to be the same as I(rebuild_protection).
+ - 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 I(delete_protection).
+ - Needs to be the same as O(delete_protection).
type: bool
placement_group:
description:
- - Placement Group of the server.
+ - Hetzner Cloud Placement Group (name or ID) to create the server in.
type: str
state:
description:
@@ -336,9 +336,9 @@ hcloud_server:
"""
from datetime import datetime, timedelta, timezone
+from typing import TYPE_CHECKING, Literal
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.common.text.converters import to_native
from ..module_utils.hcloud import AnsibleHCloud
from ..module_utils.vendor.hcloud import HCloudException
@@ -348,8 +348,14 @@ from ..module_utils.vendor.hcloud.servers import (
Server,
ServerCreatePublicNetwork,
)
-from ..module_utils.vendor.hcloud.ssh_keys import SSHKey
-from ..module_utils.vendor.hcloud.volumes import Volume
+
+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):
@@ -358,38 +364,31 @@ class AnsibleHCloudServer(AnsibleHCloud):
hcloud_server: BoundServer | None = None
def _prepare_result(self):
- image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name)
- placement_group = (
- None if self.hcloud_server.placement_group is None else to_native(self.hcloud_server.placement_group.name)
- )
- ipv4_address = (
- None if self.hcloud_server.public_net.ipv4 is None else to_native(self.hcloud_server.public_net.ipv4.ip)
- )
- ipv6 = None if self.hcloud_server.public_net.ipv6 is None else to_native(self.hcloud_server.public_net.ipv6.ip)
- backup_window = (
- None if self.hcloud_server.backup_window is None else to_native(self.hcloud_server.backup_window)
- )
return {
- "id": to_native(self.hcloud_server.id),
- "name": to_native(self.hcloud_server.name),
- "created": to_native(self.hcloud_server.created.isoformat()),
- "ipv4_address": ipv4_address,
- "ipv6": ipv6,
- "private_networks": [to_native(net.network.name) for net in self.hcloud_server.private_net],
+ "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": to_native(net.network.name), "ip": net.ip} for net in self.hcloud_server.private_net
+ {"name": net.network.name, "ip": net.ip} for net in self.hcloud_server.private_net
],
- "image": image,
- "server_type": to_native(self.hcloud_server.server_type.name),
- "datacenter": to_native(self.hcloud_server.datacenter.name),
- "location": to_native(self.hcloud_server.datacenter.location.name),
- "placement_group": placement_group,
+ "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": backup_window,
+ "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": to_native(self.hcloud_server.status),
+ "status": self.hcloud_server.status,
}
def _get_server(self):
@@ -405,73 +404,76 @@ class AnsibleHCloudServer(AnsibleHCloud):
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"),
- "labels": self.module.params.get("labels"),
- "image": self._get_image(server_type),
- "placement_group": self._get_placement_group(),
"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:
- primary_ip = self.client.primary_ips.get_by_name(self.module.params.get("ipv4"))
- if not primary_ip:
- primary_ip = self.client.primary_ips.get_by_id(self.module.params.get("ipv4"))
- params["public_net"].ipv4 = primary_ip
+ 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:
- primary_ip = self.client.primary_ips.get_by_name(self.module.params.get("ipv6"))
- if not primary_ip:
- primary_ip = self.client.primary_ips.get_by_id(self.module.params.get("ipv6"))
- params["public_net"].ipv6 = primary_ip
+ 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:
- _networks = []
- for network_name_or_id in self.module.params.get("private_networks"):
- _networks.append(
- self.client.networks.get_by_name(network_name_or_id)
- or self.client.networks.get_by_id(network_name_or_id)
- )
- params["networks"] = _networks
+ 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"] = [SSHKey(name=ssh_key_name) for ssh_key_name in self.module.params.get("ssh_keys")]
+ 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"] = [Volume(id=volume_id) for volume_id in self.module.params.get("volumes")]
+ 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"] = []
- for firewall_param in self.module.params.get("firewalls"):
- firewall = self.client.firewalls.get_by_name(firewall_param)
- if firewall is not None:
- # When firewall name is not available look for id instead
- params["firewalls"].append(firewall)
- else:
- params["firewalls"].append(self.client.firewalls.get_by_id(firewall_param))
+ 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.locations.get_by_name(self.module.params.get("location"))
+ 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.datacenters.get_by_name(self.module.params.get("datacenter"))
+ 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
- resp.action.wait_until_finished(max_retries=1000)
- [action.wait_until_finished() for action in resp.next_actions]
+ # 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:
@@ -481,40 +483,35 @@ class AnsibleHCloudServer(AnsibleHCloud):
backups = self.module.params.get("backups")
if backups:
self._get_server()
- self.hcloud_server.enable_backup().wait_until_finished()
+ 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()
- self.hcloud_server.change_protection(
+ action = self.hcloud_server.change_protection(
delete=delete_protection,
rebuild=rebuild_protection,
- ).wait_until_finished()
+ )
+ 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):
- image_resp = self.client.images.get_list(
+ 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,
)
- images = getattr(image_resp, "images")
- image = None
- if images is not None and len(images) > 0:
- # If image name is not available look for id instead
- image = images[0]
- else:
- try:
- image = self.client.images.get_by_id(self.module.params.get("image"))
- except HCloudException as exception:
- self.fail_json_hcloud(exception, msg=f"Image {self.module.params.get('image')} was not found")
+ 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("allow_deprecated_image"):
+ 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')}."
@@ -524,27 +521,18 @@ class AnsibleHCloudServer(AnsibleHCloud):
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 allow_deprecated_image=true."
+ "If you want to use this image use image_allow_deprecated=true."
)
)
return image
- def _get_server_type(self):
- server_type = self.client.server_types.get_by_name(self.module.params.get("server_type"))
- if server_type is None:
- try:
- server_type = self.client.server_types.get_by_id(self.module.params.get("server_type"))
- except HCloudException as exception:
- self.fail_json_hcloud(
- exception,
- msg=f"server_type {self.module.params.get('server_type')} was not found",
- )
+ 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):
+ def _check_and_warn_deprecated_server(self, server_type: ServerType) -> None:
if server_type.deprecation is None:
return
@@ -567,42 +555,16 @@ class AnsibleHCloudServer(AnsibleHCloud):
"the server_type parameter on the hetzner.hcloud.server module."
)
- def _get_placement_group(self):
- if self.module.params.get("placement_group") is None:
- return None
-
- placement_group = self.client.placement_groups.get_by_name(self.module.params.get("placement_group"))
- if placement_group is None:
- try:
- placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group"))
- except HCloudException as exception:
- self.fail_json_hcloud(
- exception,
- msg=f"placement_group {self.module.params.get('placement_group')} was not found",
- )
-
- return placement_group
-
- def _get_primary_ip(self, field):
- if self.module.params.get(field) is None:
- return None
-
- primary_ip = self.client.primary_ips.get_by_name(self.module.params.get(field))
- if primary_ip is None:
- try:
- primary_ip = self.client.primary_ips.get_by_id(self.module.params.get(field))
- except HCloudException as exception:
- self.fail_json_hcloud(exception, msg=f"primary_ip {self.module.params.get(field)} was not found")
-
- return primary_ip
-
- def _update_server(self):
- if "force_upgrade" in self.module.params and self.module.params.get("force_upgrade") is not None:
- self.module.warn("force_upgrade is deprecated, use force instead")
-
+ 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:
@@ -610,167 +572,39 @@ class AnsibleHCloudServer(AnsibleHCloud):
self._mark_as_changed()
elif not rescue_mode and self.hcloud_server.rescue_enabled is True:
if not self.module.check_mode:
- self.hcloud_server.disable_rescue().wait_until_finished()
+ 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:
- self.hcloud_server.enable_backup().wait_until_finished()
+ 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:
- self.hcloud_server.disable_backup().wait_until_finished()
+ action = self.hcloud_server.disable_backup()
+ action.wait_until_finished()
self._mark_as_changed()
- 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()
+ if self.module.params.get("firewalls") is not None:
+ self._update_server_firewalls()
- wanted_firewalls = self.module.params.get("firewalls")
- if wanted_firewalls is not None:
- # Removing existing but not wanted firewalls
- for current_firewall in self.hcloud_server.public_net.firewalls:
- if current_firewall.firewall.name not in wanted_firewalls:
- self._mark_as_changed()
- if self.module.check_mode:
- continue
-
- firewall_resource = FirewallResource(type="server", server=self.hcloud_server)
- actions = self.client.firewalls.remove_from_resources(
- current_firewall.firewall,
- [firewall_resource],
- )
- for action in actions:
- action.wait_until_finished()
-
- # Adding wanted firewalls that doesn't exist yet
- for firewall_name in wanted_firewalls:
- found = False
- for firewall in self.hcloud_server.public_net.firewalls:
- if firewall.firewall.name == firewall_name:
- found = True
- break
-
- if not found:
- self._mark_as_changed()
- if not self.module.check_mode:
- firewall = self.client.firewalls.get_by_name(firewall_name)
- if firewall is None:
- self.module.fail_json(msg=f"firewall {firewall_name} was not found")
- firewall_resource = FirewallResource(type="server", server=self.hcloud_server)
- actions = self.client.firewalls.apply_to_resources(firewall, [firewall_resource])
- for action in actions:
- action.wait_until_finished()
-
- if "placement_group" in self.module.params:
- if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None:
- if not self.module.check_mode:
- self.hcloud_server.remove_from_placement_group().wait_until_finished()
- self._mark_as_changed()
- else:
- placement_group = self._get_placement_group()
- if placement_group is not None and (
- self.hcloud_server.placement_group is None
- or self.hcloud_server.placement_group.id != placement_group.id
- ):
- self.stop_server_if_forced()
- if not self.module.check_mode:
- self.hcloud_server.add_to_placement_group(placement_group).wait_until_finished()
- self._mark_as_changed()
-
- if "ipv4" in self.module.params:
- if (
- self.module.params["ipv4"] is None
- and self.hcloud_server.public_net.primary_ipv4 is not None
- and not self.module.params.get("enable_ipv4")
- ):
- self.stop_server_if_forced()
- if not self.module.check_mode:
- self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished()
- self._mark_as_changed()
- else:
- primary_ip = self._get_primary_ip("ipv4")
- if primary_ip is not None and (
- self.hcloud_server.public_net.primary_ipv4 is None
- or self.hcloud_server.public_net.primary_ipv4.id != primary_ip.id
- ):
- self.stop_server_if_forced()
- if not self.module.check_mode:
- if self.hcloud_server.public_net.primary_ipv4:
- self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished()
- primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished()
- self._mark_as_changed()
- if "ipv6" in self.module.params:
- if (
- (self.module.params["ipv6"] is None or self.module.params["ipv6"] == "")
- and self.hcloud_server.public_net.primary_ipv6 is not None
- and not self.module.params.get("enable_ipv6")
- ):
- self.stop_server_if_forced()
- if not self.module.check_mode:
- self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished()
- self._mark_as_changed()
- else:
- primary_ip = self._get_primary_ip("ipv6")
- if primary_ip is not None and (
- self.hcloud_server.public_net.primary_ipv6 is None
- or self.hcloud_server.public_net.primary_ipv6.id != primary_ip.id
- ):
- self.stop_server_if_forced()
- if not self.module.check_mode:
- if self.hcloud_server.public_net.primary_ipv6 is not None:
- self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished()
- primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished()
- self._mark_as_changed()
- if "private_networks" in self.module.params and self.module.params["private_networks"] is not None:
- if not bool(self.module.params["private_networks"]):
- # This handles None, "" and []
- networks_target = {}
- else:
- _networks = {}
- for network_name_or_id in self.module.params.get("private_networks"):
- _found_network = self.client.networks.get_by_name(
- network_name_or_id
- ) or self.client.networks.get_by_id(network_name_or_id)
- _networks.update({_found_network.id: _found_network})
- networks_target = _networks
- networks_is = dict()
- for p_network in self.hcloud_server.private_net:
- networks_is.update({p_network.network.id: p_network.network})
- for network_id in set(list(networks_is) + list(networks_target)):
- if network_id in networks_is and network_id not in networks_target:
- self.stop_server_if_forced()
- if not self.module.check_mode:
- self.hcloud_server.detach_from_network(networks_is[network_id]).wait_until_finished()
- self._mark_as_changed()
- elif network_id in networks_target and network_id not in networks_is:
- self.stop_server_if_forced()
- if not self.module.check_mode:
- self.hcloud_server.attach_to_network(networks_target[network_id]).wait_until_finished()
- self._mark_as_changed()
-
- server_type = self.module.params.get("server_type")
- if server_type is not None:
- if self.hcloud_server.server_type.name == server_type:
- # Check if we should warn for using an deprecated server type
- self._check_and_warn_deprecated_server(self.hcloud_server.server_type)
-
- else:
- # Server type should be changed
- self.stop_server_if_forced()
-
- timeout = 100
- if self.module.params.get("upgrade_disk"):
- timeout = 1000 # When we upgrade the disk to the resize progress takes some more time.
- if not self.module.check_mode:
- self.hcloud_server.change_type(
- server_type=self._get_server_type(),
- upgrade_disk=self.module.params.get("upgrade_disk"),
- ).wait_until_finished(timeout)
- self._mark_as_changed()
+ 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)
@@ -785,15 +619,197 @@ class AnsibleHCloudServer(AnsibleHCloud):
or rebuild_protection != self.hcloud_server.protection["rebuild"]
):
if not self.module.check_mode:
- self.hcloud_server.change_protection(
+ action = self.hcloud_server.change_protection(
delete=delete_protection,
rebuild=rebuild_protection,
- ).wait_until_finished()
+ )
+ 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(
@@ -813,7 +829,8 @@ class AnsibleHCloudServer(AnsibleHCloud):
if self.hcloud_server:
if self.hcloud_server.status != Server.STATUS_RUNNING:
if not self.module.check_mode:
- self.client.servers.power_on(self.hcloud_server).wait_until_finished()
+ action = self.client.servers.power_on(self.hcloud_server)
+ action.wait_until_finished()
self._mark_as_changed()
self._get_server()
except HCloudException as exception:
@@ -824,7 +841,8 @@ class AnsibleHCloudServer(AnsibleHCloud):
if self.hcloud_server:
if self.hcloud_server.status != Server.STATUS_OFF:
if not self.module.check_mode:
- self.client.servers.power_off(self.hcloud_server).wait_until_finished()
+ action = self.client.servers.power_off(self.hcloud_server)
+ action.wait_until_finished()
self._mark_as_changed()
self._get_server()
except HCloudException as exception:
@@ -833,18 +851,14 @@ class AnsibleHCloudServer(AnsibleHCloud):
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_upgrade")
- or self.module.params.get("force")
- or self.module.params.get("state") == "stopped"
- ):
+ 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
- else:
- 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."
- )
+
+ 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
@@ -874,7 +888,8 @@ class AnsibleHCloudServer(AnsibleHCloud):
self._get_server()
if self.hcloud_server is not None:
if not self.module.check_mode:
- self.client.servers.delete(self.hcloud_server).wait_until_finished()
+ action = self.client.servers.delete(self.hcloud_server)
+ action.wait_until_finished()
self._mark_as_changed()
self.hcloud_server = None
except HCloudException as exception:
@@ -887,6 +902,7 @@ class AnsibleHCloudServer(AnsibleHCloud):
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"},
@@ -902,9 +918,14 @@ class AnsibleHCloudServer(AnsibleHCloud):
ipv4={"type": "str"},
ipv6={"type": "str"},
private_networks={"type": "list", "elements": "str", "default": None},
- force={"type": "bool", "default": False},
- force_upgrade={"type": "bool"},
- allow_deprecated_image={"type": "bool", "default": False},
+ 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"},