mirror of
https://github.com/ansible-collections/hetzner.hcloud.git
synced 2026-02-04 08:01:49 +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
254
tests/unit/module_utils/test_deprecation.py
Normal file
254
tests/unit/module_utils/test_deprecation.py
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from ansible_collections.hetzner.hcloud.plugins.module_utils.deprecation import (
|
||||
deprecated_server_type_warning,
|
||||
)
|
||||
from ansible_collections.hetzner.hcloud.plugins.module_utils.vendor.hcloud.locations import (
|
||||
BoundLocation,
|
||||
)
|
||||
from ansible_collections.hetzner.hcloud.plugins.module_utils.vendor.hcloud.server_types import (
|
||||
BoundServerType,
|
||||
)
|
||||
|
||||
PAST = datetime.now(timezone.utc) - timedelta(days=14)
|
||||
FUTURE = datetime.now(timezone.utc) + timedelta(days=14)
|
||||
|
||||
LOCATION_FSN = {
|
||||
"id": 1,
|
||||
"name": "fsn1",
|
||||
}
|
||||
LOCATION_NBG = {
|
||||
"id": 2,
|
||||
"name": "nbg1",
|
||||
}
|
||||
DEPRECATION_NONE = {
|
||||
"deprecation": None,
|
||||
}
|
||||
DEPRECATION_DEPRECATED = {
|
||||
"deprecation": {
|
||||
"announced": PAST.isoformat(),
|
||||
"unavailable_after": FUTURE.isoformat(),
|
||||
},
|
||||
}
|
||||
DEPRECATION_UNAVAILABLE = {
|
||||
"deprecation": {
|
||||
"announced": PAST.isoformat(),
|
||||
"unavailable_after": PAST.isoformat(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("server_type", "location", "calls"),
|
||||
[
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{"name": "cx22", "locations": []},
|
||||
),
|
||||
BoundLocation(mock.Mock(), LOCATION_FSN),
|
||||
[],
|
||||
),
|
||||
# - Deprecated (backward compatible)
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{"name": "cx22", **DEPRECATION_DEPRECATED},
|
||||
),
|
||||
None,
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is deprecated in all locations and will no longer "
|
||||
f"be available for order as of {FUTURE.strftime('%Y-%m-%d')}. "
|
||||
"Existing servers of that type will continue to work as before and "
|
||||
"no action is required on your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
# - Unavailable (backward compatible)
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{"name": "cx22", **DEPRECATION_UNAVAILABLE},
|
||||
),
|
||||
None,
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is unavailable in all locations and can no longer "
|
||||
"be ordered. Existing servers of that type will continue to work as "
|
||||
"before and no action is required on your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
# - SOME locations are deprecated
|
||||
# - Given location is NOT deprecated
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_NONE},
|
||||
{**LOCATION_NBG, **DEPRECATION_DEPRECATED},
|
||||
],
|
||||
},
|
||||
),
|
||||
BoundLocation(mock.Mock(), LOCATION_FSN),
|
||||
[],
|
||||
),
|
||||
# - SOME locations are deprecated
|
||||
# - Given location is deprecated
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_NONE},
|
||||
{**LOCATION_NBG, **DEPRECATION_DEPRECATED},
|
||||
],
|
||||
},
|
||||
),
|
||||
BoundLocation(mock.Mock(), LOCATION_NBG),
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is deprecated in nbg1 and will no longer be available "
|
||||
f"for order as of {FUTURE.strftime('%Y-%m-%d')}. Existing servers of "
|
||||
"that type will continue to work as before and no action is required "
|
||||
"on your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
# - SOME locations are unavailable
|
||||
# - Given location is unavailable
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_NONE},
|
||||
{**LOCATION_NBG, **DEPRECATION_UNAVAILABLE},
|
||||
],
|
||||
},
|
||||
),
|
||||
BoundLocation(mock.Mock(), LOCATION_NBG),
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is unavailable in nbg1 and can no longer be ordered. "
|
||||
"Existing servers of that type will continue to work as before and no "
|
||||
"action is required on your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
# - SOME locations are deprecated
|
||||
# - Location is not given
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_NONE},
|
||||
{**LOCATION_NBG, **DEPRECATION_DEPRECATED},
|
||||
],
|
||||
},
|
||||
),
|
||||
None,
|
||||
[],
|
||||
),
|
||||
# - SOME locations are unavailable
|
||||
# - Location is not given
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_NONE},
|
||||
{**LOCATION_NBG, **DEPRECATION_UNAVAILABLE},
|
||||
],
|
||||
},
|
||||
),
|
||||
None,
|
||||
[],
|
||||
),
|
||||
# - SOME locations are deprecated
|
||||
# - SOME locations are unavailable
|
||||
# - Location is not given
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_DEPRECATED},
|
||||
{**LOCATION_NBG, **DEPRECATION_UNAVAILABLE},
|
||||
],
|
||||
},
|
||||
),
|
||||
None,
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is deprecated in all locations (fsn1,nbg1) and "
|
||||
"can no longer be ordered in some locations (nbg1). Existing servers"
|
||||
" of that type will continue to work as before and no action is "
|
||||
"required on your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
# - ALL locations are deprecated
|
||||
# - Location is not given
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_DEPRECATED},
|
||||
{**LOCATION_NBG, **DEPRECATION_DEPRECATED},
|
||||
],
|
||||
},
|
||||
),
|
||||
None,
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is deprecated in all locations (fsn1,nbg1) and "
|
||||
"will no longer be available for order. Existing servers of that "
|
||||
"type will continue to work as before and no action is required on "
|
||||
"your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
# - ALL locations are unavailable
|
||||
# - Location is not given
|
||||
(
|
||||
BoundServerType(
|
||||
mock.Mock(),
|
||||
{
|
||||
"name": "cx22",
|
||||
"locations": [
|
||||
{**LOCATION_FSN, **DEPRECATION_UNAVAILABLE},
|
||||
{**LOCATION_NBG, **DEPRECATION_UNAVAILABLE},
|
||||
],
|
||||
},
|
||||
),
|
||||
None,
|
||||
[
|
||||
mock.call(
|
||||
"Server type cx22 is unavailable in all locations (fsn1,nbg1) and "
|
||||
"can no longer be ordered. Existing servers of that type will "
|
||||
"continue to work as before and no action is required on your part."
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_deprecated_server_type_warning(server_type, location, calls):
|
||||
m = mock.Mock()
|
||||
deprecated_server_type_warning(m, server_type, location)
|
||||
m.warn.assert_has_calls(calls)
|
||||
Loading…
Add table
Add a link
Reference in a new issue