diff --git a/changelogs/fragments/phasing-out-datacenters.yml b/changelogs/fragments/phasing-out-datacenters.yml new file mode 100644 index 0000000..07f3494 --- /dev/null +++ b/changelogs/fragments/phasing-out-datacenters.yml @@ -0,0 +1,32 @@ +release_summary: | + This release is phasing out datacenters in ``Primary IPs`` and ``Servers``. + + We added a new ``location`` property to the request body and response of ``Servers`` and ``Primary IPs``. + The same data was previously present under ``datacenter.location``. + + We deprecated the ``datacenter`` property in the request body and response of ``Servers`` and ``Primary IPs``. + The removal will happen after 1 July 2026. + + See our `changelog`_ for more details. + + .. _changelog: https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters + +minor_changes: + - primary_ip - Added the ``location`` argument to create a Primary IP in a specific location. + - primary_ip - Added the Primary IP ``location`` name to the return values (``hcloud_primary_ip.location``). + - primary_ip_info - Added the Primary IPs ``location`` name to the return values (``hcloud_primary_ip_info[].location``). + +deprecated_features: + - hcloud inventory - The ``hcloud_datacenter`` host variable is deprecated and will be removed after 1 July 2026. Please use the ``hcloud_location`` host variable instead. + + - primary_ip - The ``datacenter`` argument is deprecated and will be removed after 1 July 2026. Please use the ``location`` argument instead. + - primary_ip - The ``hcloud_primary_ip.datacenter`` return value is deprecated and will be removed after 1 July 2026. Please use the ``hcloud_primary_ip.location`` return value instead. + + - primary_ip_info - The ``hcloud_primary_ip_info[].datacenter`` return value is deprecated and will be removed after 1 July 2026. Please use the ``hcloud_primary_ip_info[].location`` return value instead. + + - server - The ``datacenter`` argument is deprecated and will be removed after 1 July 2026. Please use the ``location`` argument instead. + - server - The ``hcloud_server.datacenter`` return value is deprecated and will be removed after 1 July 2026. Please use the ``hcloud_server.location`` return value instead. + + - server_info - The ``hcloud_server_info[].datacenter`` return value is deprecated and will be removed after 1 July 2026. Please use the ``hcloud_server_info[].location`` return value instead. + + - network_info - The ``hcloud_network_info[].servers[].datacenter`` return value is deprecated and will be removed after 1 July 2026. Please use the ``hcloud_network_info[].servers[].location`` return value instead. diff --git a/plugins/inventory/hcloud.py b/plugins/inventory/hcloud.py index 0fb8981..93603f3 100644 --- a/plugins/inventory/hcloud.py +++ b/plugins/inventory/hcloud.py @@ -171,9 +171,12 @@ plugin: hetzner.hcloud.hcloud # hcloud_image_id: 114690387 # hcloud_image_name: "debian-12" # hcloud_image_os_flavor: "debian" -## Datacenter -# hcloud_datacenter: "hel1-dc2" +## Location # hcloud_location: "hel1" +# # The hcloud_datacenter variable is deprecated and will be removed after 1 July 2026. +# # Please use the hcloud_location variable instead. +# # See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. +# hcloud_datacenter: "hel1-dc2" ## Network # hcloud_ipv4: "65.109.140.95" # Value is optional! # hcloud_ipv6: "2a01:4f9:c011:b83f::1" # Value is optional! @@ -185,10 +188,11 @@ plugin: hetzner.hcloud.hcloud # name: "my-private-network" # ip: "10.0.0.3" # -hostname: "my-prefix-{{ hcloud_datacenter }}-{{ hcloud_name }}-{{ hcloud_server_type }}" +hostname: "my-prefix-{{ hcloud_location }}-{{ hcloud_name }}-{{ hcloud_server_type }}" """ import sys +import warnings from ipaddress import IPv6Network from ansible.errors import AnsibleError @@ -230,7 +234,7 @@ if sys.version_info >= (3, 11): architecture: str # Datacenter - datacenter: str + datacenter: str # Deprecated! location: str # Labels @@ -322,7 +326,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): if self.get_option("locations"): locations: list[str] = self.get_option("locations") - servers = [s for s in servers if s.datacenter.location.name in locations] + servers = [s for s in servers if s.location.name in locations] if self.get_option("types"): server_types: list[str] = self.get_option("types") @@ -365,9 +369,16 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): server_dict["private_ipv4"] = private_net.ip break - # Datacenter - server_dict["datacenter"] = server.datacenter.name - server_dict["location"] = server.datacenter.location.name + # Location + server_dict["location"] = server.location.name + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + self.display.warning( + "The `hcloud_datacenter` variable is deprecated and will be removed " + "after 1 July 2026. Please use the `hcloud_location` variable instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters." + ) + server_dict["datacenter"] = server.datacenter and server.datacenter.name # Image if server.image is not None: diff --git a/plugins/module_utils/primary_ip.py b/plugins/module_utils/primary_ip.py index 9095796..7172711 100644 --- a/plugins/module_utils/primary_ip.py +++ b/plugins/module_utils/primary_ip.py @@ -11,7 +11,8 @@ def prepare_result(o: BoundPrimaryIP): "name": o.name, "ip": o.ip, "type": o.type, - "datacenter": o.datacenter.name, + "location": o.location.name, + "datacenter": o.datacenter and o.datacenter.name, "labels": o.labels, "delete_protection": o.protection["delete"], "assignee_id": o.assignee_id, diff --git a/plugins/modules/network_info.py b/plugins/modules/network_info.py index 8bbd47a..2f0289e 100644 --- a/plugins/modules/network_info.py +++ b/plugins/modules/network_info.py @@ -155,7 +155,12 @@ hcloud_network_info: type: str sample: fsn1 datacenter: - description: Name of the datacenter of the server + description: | + Name of the datacenter of the server + + B(Deprecated:) The RV(hcloud_network_info[].servers[].datacenter) value is deprecated and will be removed + after 1 July 2026. Please use the RV(hcloud_network_info[].servers[].location) value instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. returned: always type: str sample: fsn1-dc14 @@ -227,8 +232,8 @@ class AnsibleHCloudNetworkInfo(AnsibleHCloud): "ipv6": server.public_net.ipv6.ip if server.public_net.ipv6 is not None else None, "image": server.image.name if server.image is not None else None, "server_type": server.server_type.name, - "datacenter": server.datacenter.name, - "location": server.datacenter.location.name, + "datacenter": server.datacenter and server.datacenter.name, + "location": server.location.name, "rescue_enabled": server.rescue_enabled, "backup_window": server.backup_window, "labels": server.labels, diff --git a/plugins/modules/primary_ip.py b/plugins/modules/primary_ip.py index a09faa1..9ad81f6 100644 --- a/plugins/modules/primary_ip.py +++ b/plugins/modules/primary_ip.py @@ -24,17 +24,25 @@ options: id: description: - The ID of the Hetzner Cloud Primary IPs to manage. - - Only required if no Primary IP I(name) is given. + - Only required if no Primary IP O(name) is given. type: int name: description: - The Name of the Hetzner Cloud Primary IPs to manage. - - Only required if no Primary IP I(id) is given or a Primary IP does not exist. + - Only required if no Primary IP O(id) is given or a Primary IP does not exist. + type: str + location: + description: + - ID or name of the Location the Hetzner Cloud Primary IP will be bound to. + - Required if no O(server) or O(datacenter) is given and Primary IP does not exist. type: str datacenter: description: + - B(Deprecated:) The O(datacenter) argument is deprecated and will be removed + after 1 July 2026. Please use the O(location) argument instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. - Home Location of the Hetzner Cloud Primary IP. - - Required if no I(server) is given and Primary IP does not exist. + - Required if no O(server) or O(location) is given and Primary IP does not exist. type: str server: description: @@ -76,14 +84,14 @@ EXAMPLES = """ - name: Create a IPv4 Primary IP hetzner.hcloud.primary_ip: name: my-primary-ip - datacenter: fsn1-dc14 + location: fsn1 type: ipv4 state: present - name: Create a IPv6 Primary IP hetzner.hcloud.primary_ip: name: my-primary-ip - datacenter: fsn1-dc14 + location: fsn1 type: ipv6 state: present @@ -134,8 +142,18 @@ hcloud_primary_ip: type: str returned: Always sample: ipv4 + location: + description: Name of the Location of the Primary IP + type: str + returned: Always + sample: fsn1 datacenter: - description: Name of the datacenter of the Primary IP + description: | + Name of the datacenter of the Primary IP + + B(Deprecated:) The RV(hcloud_primary_ip.datacenter) value is deprecated and will be removed + after 1 July 2026. Please use the RV(hcloud_primary_ip.location) value instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. type: str returned: Always sample: fsn1-dc14 @@ -198,15 +216,26 @@ class AnsiblePrimaryIP(AnsibleHCloud): def _create(self): self.fail_on_invalid_params( required=["name", "type"], - required_one_of=[["server", "datacenter"]], + required_one_of=[["server", "location", "datacenter"]], ) params = { "name": self.module.params.get("name"), "type": self.module.params.get("type"), } - if (value := self.module.params.get("datacenter")) is not None: - params["datacenter"] = self.client.datacenters.get_by_name(value) + if (value := self.module.params.get("location")) is not None: + params["location"] = self._client_get_by_name_or_id("locations", value) + elif (value := self.module.params.get("datacenter")) is not None: + self.module.warn( + "The `datacenter` argument is deprecated and will be removed " + "after 1 July 2026. Please use the `location` argument instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters." + ) + # Backward compatible datacenter argument. + # datacenter hel1-dc2 => location hel1 + # pylint: disable=disallowed-name + part1, _, _ = str(value).partition("-") + params["location"] = self.client.locations.get_by_name(part1) elif (value := self.module.params.get("server")) is not None: server: BoundServer = self._client_get_by_name_or_id("servers", value) params["assignee_id"] = server.id @@ -287,7 +316,12 @@ class AnsiblePrimaryIP(AnsibleHCloud): argument_spec=dict( id={"type": "int"}, name={"type": "str"}, - datacenter={"type": "str"}, + location={"type": "str"}, + datacenter={ + "type": "str", + "removed_at_date": "2026-07-01", + "removed_from_collection": "hetzner.hcloud", + }, server={"type": "str"}, auto_delete={"type": "bool", "default": False}, type={"choices": ["ipv4", "ipv6"]}, diff --git a/plugins/modules/primary_ip_info.py b/plugins/modules/primary_ip_info.py index 8d3cdb4..b2ecb8f 100644 --- a/plugins/modules/primary_ip_info.py +++ b/plugins/modules/primary_ip_info.py @@ -99,8 +99,18 @@ hcloud_primary_ip_info: returned: always type: str sample: server + location: + description: Location where the Primary IP was created in. + returned: always + type: str + sample: fsn1 home_location: - description: Location with datacenter where the Primary IP was created in + description: | + Datacenter where the Primary IP was created in. + + B(Deprecated:) The RV(hcloud_primary_ip_info[].home_location) value is deprecated and will be removed + after 1 July 2026. Please use the RV(hcloud_primary_ip_info[].location) value instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. returned: always type: str sample: fsn1-dc1 @@ -152,7 +162,8 @@ class AnsibleHCloudPrimaryIPInfo(AnsibleHCloud): "assignee_id": primary_ip.assignee_id if primary_ip.assignee_id is not None else None, "assignee_type": primary_ip.assignee_type, "auto_delete": primary_ip.auto_delete, - "home_location": primary_ip.datacenter.name, + "location": primary_ip.location.name, + "home_location": primary_ip.datacenter and primary_ip.datacenter.name, "dns_ptr": primary_ip.dns_ptr[0]["dns_ptr"] if len(primary_ip.dns_ptr) else None, "labels": primary_ip.labels, "delete_protection": primary_ip.protection["delete"], diff --git a/plugins/modules/server.py b/plugins/modules/server.py index d427d48..c3ef644 100644 --- a/plugins/modules/server.py +++ b/plugins/modules/server.py @@ -67,13 +67,16 @@ options: 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. + - Required if the server does not exist. - Only used during the server creation. type: str datacenter: description: + - B(Deprecated:) The O(datacenter) argument is deprecated and will be removed + after 1 July 2026. Please use the O(location) argument instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. - Hetzner Cloud Datacenter (name or ID) to create the server in. - - Required if no O(location) is given and server does not exist. + - Required if no O(location) is given and the server does not exist. - Only used during the server creation. type: str backups: @@ -303,7 +306,12 @@ hcloud_server: sample: 4711 version_added: "1.5.0" datacenter: - description: Name of the datacenter of the server + description: | + Name of the datacenter of the server. + + B(Deprecated:) The RV(hcloud_server.datacenter) value is deprecated and will be removed + after 1 July 2026. Please use the RV(hcloud_server.location) value instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. returned: always type: str sample: fsn1-dc14 @@ -384,8 +392,8 @@ class AnsibleHCloudServer(AnsibleHCloud): ], "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, + "datacenter": self.hcloud_server.datacenter and self.hcloud_server.datacenter.name, + "location": self.hcloud_server.location.name, "placement_group": ( self.hcloud_server.placement_group.name if self.hcloud_server.placement_group is not None else None ), @@ -469,8 +477,22 @@ class AnsibleHCloudServer(AnsibleHCloud): params["location"] = self._client_get_by_name_or_id("locations", self.module.params.get("location")) server_type_location = params["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")) - server_type_location = params["datacenter"].location + self.module.warn( + "The `datacenter` argument is deprecated and will be removed " + "after 1 July 2026. Please use the `location` argument instead. " + "See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters." + ) + value: str = self.module.params.get("datacenter") + # Backward compatible datacenter argument. + # datacenter hel1-dc2 => location hel1 + if value and not value.isdigit(): + # pylint: disable=disallowed-name + part1, _, _ = value.partition("-") + params["location"] = self.client.locations.get_by_name(part1) + server_type_location = params["location"] + else: + params["datacenter"] = self._client_get_by_name_or_id("datacenters", value) + server_type_location = params["datacenter"].location if self.module.params.get("state") == "stopped" or self.module.params.get("state") == "created": params["start_after_create"] = False @@ -489,7 +511,7 @@ class AnsibleHCloudServer(AnsibleHCloud): deprecated_server_type_warning( self.module, resp.server.server_type, - resp.server.datacenter.location, + resp.server.location, ) self.result["root_password"] = resp.root_password @@ -679,7 +701,7 @@ class AnsibleHCloudServer(AnsibleHCloud): deprecated_server_type_warning( self.module, self.hcloud_server.server_type, - self.hcloud_server.datacenter.location, + self.hcloud_server.location, ) return @@ -689,7 +711,7 @@ class AnsibleHCloudServer(AnsibleHCloud): deprecated_server_type_warning( self.module, server_type, - self.hcloud_server.datacenter.location, + self.hcloud_server.location, ) self.stop_server_if_forced() @@ -936,7 +958,11 @@ class AnsibleHCloudServer(AnsibleHCloud): image_allow_deprecated={"type": "bool", "default": False, "aliases": ["allow_deprecated_image"]}, server_type={"type": "str"}, location={"type": "str"}, - datacenter={"type": "str"}, + datacenter={ + "type": "str", + "removed_at_date": "2026-07-01", + "removed_from_collection": "hetzner.hcloud", + }, user_data={"type": "str"}, ssh_keys={"type": "list", "elements": "str", "no_log": False}, volumes={"type": "list", "elements": "str"}, diff --git a/plugins/modules/server_info.py b/plugins/modules/server_info.py index f709fe2..8e97d6e 100644 --- a/plugins/modules/server_info.py +++ b/plugins/modules/server_info.py @@ -113,7 +113,12 @@ hcloud_server_info: sample: 4711 version_added: "1.5.0" datacenter: - description: Name of the datacenter of the server + description: | + Name of the datacenter of the server. + + B(Deprecated:) The RV(hcloud_server_info[].datacenter) value is deprecated and will be removed + after 1 July 2026. Please use the RV(hcloud_server_info[].location) value instead. + See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters. returned: always type: str sample: fsn1-dc14 @@ -175,8 +180,8 @@ class AnsibleHCloudServerInfo(AnsibleHCloud): "private_networks_info": [{"name": net.network.name, "ip": net.ip} for net in server.private_net], "image": server.image.name if server.image is not None else None, "server_type": server.server_type.name, - "datacenter": server.datacenter.name, - "location": server.datacenter.location.name, + "datacenter": server.datacenter and server.datacenter.name, + "location": server.location.name, "placement_group": server.placement_group.name if server.placement_group is not None else None, "rescue_enabled": server.rescue_enabled, "backup_window": server.backup_window, diff --git a/tests/integration/targets/server/tasks/test.yml b/tests/integration/targets/server/tasks/test.yml index e00bcce..2aa253a 100644 --- a/tests/integration/targets/server/tasks/test.yml +++ b/tests/integration/targets/server/tasks/test.yml @@ -3,6 +3,7 @@ --- #- ansible.builtin.include_tasks: test_validation.yml - ansible.builtin.include_tasks: test_basic.yml +- ansible.builtin.include_tasks: test_datacenter.yml #- ansible.builtin.include_tasks: test_firewalls.yml - ansible.builtin.include_tasks: test_primary_ips.yml - ansible.builtin.include_tasks: test_private_network_only.yml diff --git a/tests/integration/targets/server/tasks/test_datacenter.yml b/tests/integration/targets/server/tasks/test_datacenter.yml new file mode 100644 index 0000000..8dc50fc --- /dev/null +++ b/tests/integration/targets/server/tasks/test_datacenter.yml @@ -0,0 +1,20 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Test create server with datacenter (deprecated) + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + server_type: "{{ hcloud_server_type_name }}" + image: "{{ hcloud_image_name }}" + datacenter: "{{ hcloud_datacenter_name }}" + state: created + register: result +- name: Verify create server with datacenter (deprecated) + ansible.builtin.assert: + that: + - result is changed + +- name: Cleanup server with datacenter (deprecated) + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + state: absent diff --git a/tests/unit/inventory/test_hcloud.py b/tests/unit/inventory/test_hcloud.py index 9c5b5c9..9e58903 100644 --- a/tests/unit/inventory/test_hcloud.py +++ b/tests/unit/inventory/test_hcloud.py @@ -33,18 +33,39 @@ def test_build_inventory_server(): "blocked": False, "dns_ptr": "static.1.0.0.127.clients.your-server.de", }, - "ipv6": {"id": 56583279, "ip": "2001:db8::/64", "blocked": False, "dns_ptr": []}, + "ipv6": { + "id": 56583279, + "ip": "2001:db8::/64", + "blocked": False, + "dns_ptr": [], + }, "floating_ips": [], "firewalls": [], }, "private_net": [], - "server_type": {"id": 109, "name": "cpx22", "architecture": "x86"}, + "server_type": { + "id": 109, + "name": "cpx22", + "architecture": "x86", + }, + "location": { + "id": 3, + "name": "hel1", + }, "datacenter": { "id": 3, "name": "hel1-dc2", - "location": {"id": 3, "name": "hel1"}, + "location": { + "id": 3, + "name": "hel1", + }, + }, + "image": { + "id": 114690387, + "name": "debian-12", + "os_flavor": "debian", + "os_version": "12", }, - "image": {"id": 114690387, "name": "debian-12", "os_flavor": "debian", "os_version": "12"}, }, ) # pylint: disable=protected-access