mirror of
https://github.com/ansible-collections/hetzner.hcloud.git
synced 2026-02-03 23:51:48 +00:00
feat: per location server types (#692)
[Server Types](https://docs.hetzner.cloud/reference/cloud#server-types) now depend on [Locations](https://docs.hetzner.cloud/reference/cloud#locations). - We added a new `locations` property to the [Server Types](https://docs.hetzner.cloud/reference/cloud#server-types) resource. The new property defines a list of supported [Locations](https://docs.hetzner.cloud/reference/cloud#locations) and additional per [Locations](https://docs.hetzner.cloud/reference/cloud#locations) details such as deprecations information. - We deprecated the `deprecation` property from the [Server Types](https://docs.hetzner.cloud/reference/cloud#server-types) resource. The property will gradually be phased out as per [Locations](https://docs.hetzner.cloud/reference/cloud#locations) deprecations are being announced. Please use the new per [Locations](https://docs.hetzner.cloud/reference/cloud#locations) deprecation information instead. See our [changelog](https://docs.hetzner.cloud/changelog#2025-09-24-per-location-server-types) for more details. **Upgrading** ```yaml --- - name: Validate server type hosts: localhost connection: local tasks: - name: Fetch server type info hetzner.hcloud.server_type_info: name: cx22 register: server_type - name: Ensure server type exists ansible.builtin.assert: fail_msg: server type does not exists that: - server_type.hcloud_server_type_info | count == 1 - name: Ensure server type is not deprecated ansible.builtin.assert: fail_msg: server type is deprecated that: - server_type.hcloud_server_type_info[0].deprecation is none ``` ```yaml --- - name: Validate server type hosts: localhost connection: local tasks: - name: Fetch location info hetzner.hcloud.location_info: name: fsn1 register: location - name: Fetch server type info hetzner.hcloud.server_type_info: name: cx22 register: server_type - name: Ensure server type exists ansible.builtin.assert: fail_msg: server type does not exists that: - server_type.hcloud_server_type_info | count == 1 - name: Extract server type location info ansible.builtin.set_fact: server_type_location: > {{ server_type.hcloud_server_type_info[0].locations | selectattr("name", "eq", location.hcloud_location_info[0].name) | first }} - name: Ensure server type is not deprecated ansible.builtin.assert: fail_msg: server type is deprecated in location that: - server_type_location.deprecation is none ```
This commit is contained in:
parent
4caf3e67f4
commit
826e6a5309
8 changed files with 500 additions and 36 deletions
124
plugins/module_utils/deprecation.py
Normal file
124
plugins/module_utils/deprecation.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from .vendor.hcloud.locations import BoundLocation
|
||||
from .vendor.hcloud.server_types import BoundServerType, ServerTypeLocation
|
||||
|
||||
DEPRECATED_EXISTING_SERVERS = """
|
||||
Existing servers of that type will continue to work as before and no action is \
|
||||
required on your part.
|
||||
""".strip()
|
||||
|
||||
|
||||
def deprecated_server_type_warning(
|
||||
module: AnsibleModule,
|
||||
server_type: BoundServerType,
|
||||
location: BoundLocation | None = None,
|
||||
) -> None:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
if server_type.deprecation is not None:
|
||||
if server_type.deprecation.unavailable_after < datetime.now(timezone.utc):
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is unavailable in all locations and can no longer be ordered. ",
|
||||
server_type=server_type.name,
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
else:
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is deprecated in all locations and will no longer be available "
|
||||
"for order as of {unavailable_after}. ",
|
||||
server_type=server_type.name,
|
||||
unavailable_after=server_type.deprecation.unavailable_after.strftime("%Y-%m-%d"),
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
return
|
||||
|
||||
deprecated_locations: list[ServerTypeLocation] = []
|
||||
unavailable_locations: list[ServerTypeLocation] = []
|
||||
|
||||
for o in server_type.locations or []:
|
||||
if o.deprecation is not None:
|
||||
deprecated_locations.append(o)
|
||||
if o.deprecation.unavailable_after < datetime.now(timezone.utc):
|
||||
unavailable_locations.append(o)
|
||||
|
||||
if not deprecated_locations:
|
||||
return
|
||||
|
||||
# Warn when the server type is deprecated in the given location
|
||||
if location:
|
||||
found = [o for o in deprecated_locations if location.name == o.location.name]
|
||||
if not found:
|
||||
return
|
||||
|
||||
deprecated_location = found[0]
|
||||
|
||||
if deprecated_location in unavailable_locations:
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is unavailable in {location} and can no longer be ordered. ",
|
||||
server_type=server_type.name,
|
||||
location=deprecated_location.location.name,
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
else:
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is deprecated in {location} and will no longer be available "
|
||||
"for order as of {unavailable_after}. ",
|
||||
server_type=server_type.name,
|
||||
location=deprecated_location.location.name,
|
||||
unavailable_after=deprecated_location.deprecation.unavailable_after.strftime("%Y-%m-%d"),
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# No location given, only warn when all locations are deprecated
|
||||
if len(server_type.locations) != len(deprecated_locations):
|
||||
return
|
||||
|
||||
if unavailable_locations:
|
||||
|
||||
if len(deprecated_locations) != len(unavailable_locations):
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is deprecated in all locations ({deprecated_locations}) and can no "
|
||||
"longer be ordered in some locations ({unavailable_locations}). ",
|
||||
server_type=server_type.name,
|
||||
deprecated_locations=",".join(o.location.name for o in deprecated_locations),
|
||||
unavailable_locations=",".join(o.location.name for o in unavailable_locations),
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
else:
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is unavailable in all locations ({unavailable_locations}) and can no "
|
||||
"longer be ordered. ",
|
||||
server_type=server_type.name,
|
||||
unavailable_locations=",".join(o.location.name for o in unavailable_locations),
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
else:
|
||||
module.warn(
|
||||
str.format(
|
||||
"Server type {server_type} is deprecated in all locations ({deprecated_locations}) and will no "
|
||||
"longer be available for order. ",
|
||||
server_type=server_type.name,
|
||||
deprecated_locations=",".join(o.location.name for o in deprecated_locations),
|
||||
)
|
||||
+ DEPRECATED_EXISTING_SERVERS,
|
||||
)
|
||||
|
|
@ -340,11 +340,12 @@ root_password:
|
|||
sample: YItygq1v3GYjjMomLaKc
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import timedelta
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ..module_utils.deprecation import deprecated_server_type_warning
|
||||
from ..module_utils.hcloud import AnsibleHCloud
|
||||
from ..module_utils.vendor.hcloud import HCloudException
|
||||
from ..module_utils.vendor.hcloud.firewalls import FirewallResource
|
||||
|
|
@ -408,7 +409,7 @@ class AnsibleHCloudServer(AnsibleHCloud):
|
|||
def _create_server(self):
|
||||
self.module.fail_on_missing_params(required_params=["name", "server_type", "image"])
|
||||
|
||||
server_type = self._get_server_type()
|
||||
server_type = self._client_get_by_name_or_id("server_types", self.module.params.get("server_type"))
|
||||
image = self._get_image(server_type)
|
||||
|
||||
params = {
|
||||
|
|
@ -458,18 +459,28 @@ class AnsibleHCloudServer(AnsibleHCloud):
|
|||
for name_or_id in self.module.params.get("firewalls")
|
||||
]
|
||||
|
||||
server_type_location = None
|
||||
|
||||
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"))
|
||||
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
|
||||
|
||||
if self.module.params.get("state") == "stopped" or self.module.params.get("state") == "created":
|
||||
params["start_after_create"] = False
|
||||
|
||||
deprecated_server_type_warning(
|
||||
self.module,
|
||||
server_type,
|
||||
server_type_location,
|
||||
)
|
||||
|
||||
if not self.module.check_mode:
|
||||
try:
|
||||
resp = self.client.servers.create(**params)
|
||||
|
|
@ -536,35 +547,6 @@ class AnsibleHCloudServer(AnsibleHCloud):
|
|||
)
|
||||
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
|
||||
|
|
@ -686,16 +668,29 @@ class AnsibleHCloudServer(AnsibleHCloud):
|
|||
# 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)
|
||||
deprecated_server_type_warning(
|
||||
self.module,
|
||||
self.hcloud_server.server_type,
|
||||
self.hcloud_server.datacenter.location,
|
||||
)
|
||||
return
|
||||
|
||||
server_type = self._client_get_by_name_or_id("server_types", wanted)
|
||||
|
||||
# Check if we should warn for updating to a deprecated server type
|
||||
deprecated_server_type_warning(
|
||||
self.module,
|
||||
server_type,
|
||||
self.hcloud_server.datacenter.location,
|
||||
)
|
||||
|
||||
self.stop_server_if_forced()
|
||||
|
||||
if not self.module.check_mode:
|
||||
upgrade_disk = self.module.params.get("upgrade_disk")
|
||||
|
||||
action = self.hcloud_server.change_type(
|
||||
server_type=self._get_server_type(),
|
||||
server_type=server_type,
|
||||
upgrade_disk=upgrade_disk,
|
||||
)
|
||||
# Upgrading a server takes 160 seconds on average, upgrading the disk should
|
||||
|
|
|
|||
|
|
@ -100,6 +100,36 @@ hcloud_server_type_info:
|
|||
returned: always
|
||||
type: str
|
||||
sample: x86
|
||||
locations:
|
||||
description: List of supported Locations
|
||||
returned: always
|
||||
type: list
|
||||
contains:
|
||||
id:
|
||||
description: Numeric identifier of the Location
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1
|
||||
name:
|
||||
description: Name of the Location
|
||||
returned: always
|
||||
type: str
|
||||
sample: fsn1
|
||||
deprecation:
|
||||
description: Wether the Server Type is deprecated in the Location.
|
||||
returned: when deprecated
|
||||
type: dict
|
||||
contains:
|
||||
announced:
|
||||
description: Date of the deprecation announcement.
|
||||
returned: when deprecated
|
||||
type: str
|
||||
sample: "2025-09-09T09:00:00Z"
|
||||
unavailable_after:
|
||||
description: Date after which the Server Type will be unavailable for new order.
|
||||
returned: when deprecated
|
||||
type: str
|
||||
sample: "2025-12-09T09:00:00Z"
|
||||
included_traffic:
|
||||
description: |
|
||||
Free traffic per month in bytes
|
||||
|
|
@ -113,6 +143,9 @@ hcloud_server_type_info:
|
|||
description: |
|
||||
Describes if, when & how the resources was deprecated.
|
||||
If this field is set to None the resource is not deprecated. If it has a value, it is considered deprecated.
|
||||
|
||||
B(Deprecated): This field is deprecated and will gradually be phased starting 24 September 2025. Use the locations field instead.
|
||||
See U(https://docs.hetzner.cloud/changelog#2025-09-24-per-location-server-types).
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
|
|
@ -163,6 +196,21 @@ class AnsibleHCloudServerTypeInfo(AnsibleHCloud):
|
|||
"storage_type": server_type.storage_type,
|
||||
"cpu_type": server_type.cpu_type,
|
||||
"architecture": server_type.architecture,
|
||||
"locations": [
|
||||
{
|
||||
"id": o.location.id,
|
||||
"name": o.location.name,
|
||||
"deprecation": (
|
||||
{
|
||||
"announced": o.deprecation.announced.isoformat(),
|
||||
"unavailable_after": o.deprecation.unavailable_after.isoformat(),
|
||||
}
|
||||
if o.deprecation is not None
|
||||
else None
|
||||
),
|
||||
}
|
||||
for o in server_type.locations or []
|
||||
],
|
||||
"included_traffic": server_type.included_traffic,
|
||||
"deprecation": (
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue