1
0
Fork 0
mirror of https://github.com/ansible-collections/hetzner.hcloud.git synced 2026-02-04 08:01:49 +00:00

feat: add name to Storage Box Subaccount (#787)

##### SUMMARY

Replaces the label based name workaround for Storage Box Subaccounts,
with the new Storage Box Subaccount name property managed in the API.

##### ISSUE TYPE

- Feature Pull Request


##### COMPONENT NAME

- `storage_box_subaccount`
- `storage_box_subaccount_info`
This commit is contained in:
Jonas L. 2026-01-16 14:05:34 +01:00 committed by GitHub
parent 09bff84a32
commit 2c6dbedec1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 27 additions and 69 deletions

View file

@ -0,0 +1,3 @@
minor_changes:
- storage_box_subaccount - Replace the label based name workaround, with the new Storage Box Subaccount name property in the API.
- storage_box_subaccount_info - Replace the label based name workaround, with the new Storage Box Subaccount name property in the API.

View file

@ -11,26 +11,23 @@ from ._vendor.hcloud.storage_boxes import (
NAME_LABEL_KEY = "ansible-name" NAME_LABEL_KEY = "ansible-name"
def get_by_name(storage_box: BoundStorageBox, name: str): def get_by_label_name(storage_box: BoundStorageBox, name: str):
if not name: """
raise ValueError(f"invalid storage box subaccount name: '{name}'") Kept for backward compatible upgrade from label based name.
"""
result = storage_box.get_subaccount_list( result = storage_box.get_subaccount_list(
label_selector=f"{NAME_LABEL_KEY}={name}", label_selector=f"{NAME_LABEL_KEY}={name}",
) )
if len(result.subaccounts) == 0:
return None
if len(result.subaccounts) == 1: if len(result.subaccounts) == 1:
return result.subaccounts[0] return result.subaccounts[0]
return None
raise ValueError(f"found multiple storage box subaccount with the same name: {name}")
def prepare_result(o: BoundStorageBoxSubaccount, name: str): def prepare_result(o: BoundStorageBoxSubaccount):
return { return {
"storage_box": o.storage_box.id, "storage_box": o.storage_box.id,
"id": o.id, "id": o.id,
"name": name, "name": o.name,
"description": o.description, "description": o.description,
"username": o.username, "username": o.username,
"home_directory": o.home_directory, "home_directory": o.home_directory,

View file

@ -39,10 +39,6 @@ options:
- Name of the Storage Box Subaccount to manage. - Name of the Storage Box Subaccount to manage.
- Required if no Storage Box Subaccount O(id) is given. - Required if no Storage Box Subaccount O(id) is given.
- Required if the Storage Box Subaccount does not exist. - Required if the Storage Box Subaccount does not exist.
- Because the API resource does not have this property, the name is stored
in the Storage Box Subaccount labels. This ensures that the module is
idempotent, and removes the need to use different module arguments for
create and update.
type: str type: str
password: password:
description: description:
@ -225,13 +221,11 @@ hcloud_storage_box_subaccount:
sample: "2025-12-03T13:47:47Z" sample: "2025-12-03T13:47:47Z"
""" """
import string
from ..module_utils import _storage_box, _storage_box_subaccount from ..module_utils import _storage_box, _storage_box_subaccount
from ..module_utils._base import AnsibleHCloud, AnsibleModule from ..module_utils._base import AnsibleHCloud, AnsibleModule
from ..module_utils._client import client_resource_not_found from ..module_utils._client import client_resource_not_found
from ..module_utils._experimental import storage_box_experimental_warning from ..module_utils._experimental import storage_box_experimental_warning
from ..module_utils._storage_box_subaccount import NAME_LABEL_KEY
from ..module_utils._vendor.hcloud import HCloudException from ..module_utils._vendor.hcloud import HCloudException
from ..module_utils._vendor.hcloud.storage_boxes import ( from ..module_utils._vendor.hcloud.storage_boxes import (
BoundStorageBox, BoundStorageBox,
@ -245,7 +239,6 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud):
storage_box: BoundStorageBox | None = None storage_box: BoundStorageBox | None = None
storage_box_subaccount: BoundStorageBoxSubaccount | None = None storage_box_subaccount: BoundStorageBoxSubaccount | None = None
storage_box_subaccount_name: str | None = None
def __init__(self, module: AnsibleModule): def __init__(self, module: AnsibleModule):
storage_box_experimental_warning(module) storage_box_experimental_warning(module)
@ -254,7 +247,7 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud):
def _prepare_result(self): def _prepare_result(self):
if self.storage_box_subaccount is None: if self.storage_box_subaccount is None:
return {} return {}
return _storage_box_subaccount.prepare_result(self.storage_box_subaccount, self.storage_box_subaccount_name) return _storage_box_subaccount.prepare_result(self.storage_box_subaccount)
def _fetch(self): def _fetch(self):
self.storage_box = _storage_box.get(self.client.storage_boxes, self.module.params.get("storage_box")) self.storage_box = _storage_box.get(self.client.storage_boxes, self.module.params.get("storage_box"))
@ -262,18 +255,18 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud):
if (value := self.module.params.get("id")) is not None: if (value := self.module.params.get("id")) is not None:
self.storage_box_subaccount = self.storage_box.get_subaccount_by_id(value) self.storage_box_subaccount = self.storage_box.get_subaccount_by_id(value)
elif (value := self.module.params.get("name")) is not None: elif (value := self.module.params.get("name")) is not None:
self.storage_box_subaccount = _storage_box_subaccount.get_by_name(self.storage_box, value) self.storage_box_subaccount = self.storage_box.get_subaccount_by_name(value)
# Workaround the missing name property # Backward compatible upgrade from label based name
# Get the name of the resource from the labels if self.storage_box_subaccount is None:
if self.storage_box_subaccount is not None: self.storage_box_subaccount = _storage_box_subaccount.get_by_label_name(self.storage_box, value)
self.storage_box_subaccount_name = self.storage_box_subaccount.labels.pop(NAME_LABEL_KEY)
def _create(self): def _create(self):
self.fail_on_invalid_params( self.fail_on_invalid_params(
required=["name", "home_directory", "password"], required=["name", "home_directory", "password"],
) )
params = { params = {
"name": self.module.params.get("name"),
"home_directory": self.module.params.get("home_directory"), "home_directory": self.module.params.get("home_directory"),
"password": self.module.params.get("password"), "password": self.module.params.get("password"),
} }
@ -287,19 +280,12 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud):
if (value := self.module.params.get("access_settings")) is not None: if (value := self.module.params.get("access_settings")) is not None:
params["access_settings"] = StorageBoxSubaccountAccessSettings.from_dict(value) params["access_settings"] = StorageBoxSubaccountAccessSettings.from_dict(value)
# Workaround the missing name property
# Save the name of the resource in the labels
if "labels" not in params:
params["labels"] = {}
params["labels"][NAME_LABEL_KEY] = self.module.params.get("name")
if not self.module.check_mode: if not self.module.check_mode:
resp = self.storage_box.create_subaccount(**params) resp = self.storage_box.create_subaccount(**params)
self.storage_box_subaccount = resp.subaccount self.storage_box_subaccount = resp.subaccount
resp.action.wait_until_finished() resp.action.wait_until_finished()
self.storage_box_subaccount.reload() self.storage_box_subaccount.reload()
self.storage_box_subaccount_name = self.storage_box_subaccount.labels.pop(NAME_LABEL_KEY)
self._mark_as_changed() self._mark_as_changed()
@ -324,6 +310,11 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud):
self._mark_as_changed() self._mark_as_changed()
params = {} params = {}
if (value := self.module.params.get("name")) is not None:
if value != self.storage_box_subaccount.name:
params["name"] = value
self._mark_as_changed()
if (value := self.module.params.get("description")) is not None: if (value := self.module.params.get("description")) is not None:
if value != self.storage_box_subaccount.description: if value != self.storage_box_subaccount.description:
params["description"] = value params["description"] = value
@ -334,25 +325,10 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud):
params["labels"] = value params["labels"] = value
self._mark_as_changed() self._mark_as_changed()
# Workaround the missing name property
# Preserve resource name in the labels, name update happens below
params["labels"][NAME_LABEL_KEY] = self.storage_box_subaccount_name
# Workaround the missing name property
# Update resource name in the labels
if (value := self.module.params.get("name")) is not None:
if value != self.storage_box_subaccount_name:
self.fail_on_invalid_params(required=["id"])
if "labels" not in params:
params["labels"] = self.storage_box_subaccount.labels
params["labels"][NAME_LABEL_KEY] = value
self._mark_as_changed()
# Update only if params holds changes or actions were triggered # Update only if params holds changes or actions were triggered
if params or need_reload: if params or need_reload:
if not self.module.check_mode: if not self.module.check_mode:
self.storage_box_subaccount = self.storage_box_subaccount.update(**params) self.storage_box_subaccount = self.storage_box_subaccount.update(**params)
self.storage_box_subaccount_name = self.storage_box_subaccount.labels.pop(NAME_LABEL_KEY)
def _delete(self): def _delete(self):
if not self.module.check_mode: if not self.module.check_mode:
@ -439,23 +415,6 @@ def main():
module = AnsibleStorageBoxSubaccount.define_module() module = AnsibleStorageBoxSubaccount.define_module()
o = AnsibleStorageBoxSubaccount(module) o = AnsibleStorageBoxSubaccount(module)
# Workaround the missing name property
# Validate name
if (value := module.params.get("name")) is not None:
if len(value) < 1:
module.fail_json(f"name '{value}' must be at least 1 character long")
allowed_chars = string.ascii_letters + string.digits + "-"
has_letters = False
for c in value:
if c in string.ascii_letters:
has_letters = True
if c in allowed_chars:
continue
module.fail_json(f"name '{value}' must only have allowed characters: {allowed_chars}")
if not has_letters:
module.fail_json(f"name '{value}' must contain at least one letter")
match module.params.get("state"): match module.params.get("state"):
case "reset_password": case "reset_password":
o.reset_password() o.reset_password()

View file

@ -161,7 +161,6 @@ from ansible.module_utils.basic import AnsibleModule
from ..module_utils import _storage_box, _storage_box_subaccount from ..module_utils import _storage_box, _storage_box_subaccount
from ..module_utils._base import AnsibleHCloud from ..module_utils._base import AnsibleHCloud
from ..module_utils._experimental import storage_box_experimental_warning from ..module_utils._experimental import storage_box_experimental_warning
from ..module_utils._storage_box_subaccount import NAME_LABEL_KEY
from ..module_utils._vendor.hcloud import HCloudException from ..module_utils._vendor.hcloud import HCloudException
from ..module_utils._vendor.hcloud.storage_boxes import ( from ..module_utils._vendor.hcloud.storage_boxes import (
BoundStorageBox, BoundStorageBox,
@ -184,11 +183,7 @@ class AnsibleStorageBoxSubaccountInfo(AnsibleHCloud):
for o in self.storage_box_subaccounts or []: for o in self.storage_box_subaccounts or []:
if o is not None: if o is not None:
# Workaround the missing name property result.append(_storage_box_subaccount.prepare_result(o))
# Get the name of the resource from the labels
name = o.labels.pop(NAME_LABEL_KEY)
result.append(_storage_box_subaccount.prepare_result(o, name))
return result return result
def fetch(self): def fetch(self):
@ -201,7 +196,11 @@ class AnsibleStorageBoxSubaccountInfo(AnsibleHCloud):
if (value := self.module.params.get("id")) is not None: if (value := self.module.params.get("id")) is not None:
self.storage_box_subaccounts = [self.storage_box.get_subaccount_by_id(value)] self.storage_box_subaccounts = [self.storage_box.get_subaccount_by_id(value)]
elif (value := self.module.params.get("name")) is not None: elif (value := self.module.params.get("name")) is not None:
self.storage_box_subaccounts = [_storage_box_subaccount.get_by_name(self.storage_box, value)] self.storage_box_subaccounts = [self.storage_box.get_subaccount_by_name(value)]
# Backward compatible upgrade from label based name
if not self.storage_box_subaccounts or self.storage_box_subaccounts[0] is None:
self.storage_box_subaccounts = [_storage_box_subaccount.get_by_label_name(self.storage_box, value)]
else: else:
params = {} params = {}
if (value := self.module.params.get("label_selector")) is not None: if (value := self.module.params.get("label_selector")) is not None: