diff --git a/changelogs/fragments/storage-box-subaccount-api-name.yml b/changelogs/fragments/storage-box-subaccount-api-name.yml new file mode 100644 index 0000000..5b30002 --- /dev/null +++ b/changelogs/fragments/storage-box-subaccount-api-name.yml @@ -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. diff --git a/plugins/module_utils/_storage_box_subaccount.py b/plugins/module_utils/_storage_box_subaccount.py index cec0d3f..56e2dae 100644 --- a/plugins/module_utils/_storage_box_subaccount.py +++ b/plugins/module_utils/_storage_box_subaccount.py @@ -11,26 +11,23 @@ from ._vendor.hcloud.storage_boxes import ( NAME_LABEL_KEY = "ansible-name" -def get_by_name(storage_box: BoundStorageBox, name: str): - if not name: - raise ValueError(f"invalid storage box subaccount name: '{name}'") - +def get_by_label_name(storage_box: BoundStorageBox, name: str): + """ + Kept for backward compatible upgrade from label based name. + """ result = storage_box.get_subaccount_list( label_selector=f"{NAME_LABEL_KEY}={name}", ) - if len(result.subaccounts) == 0: - return None if len(result.subaccounts) == 1: return result.subaccounts[0] - - raise ValueError(f"found multiple storage box subaccount with the same name: {name}") + return None -def prepare_result(o: BoundStorageBoxSubaccount, name: str): +def prepare_result(o: BoundStorageBoxSubaccount): return { "storage_box": o.storage_box.id, "id": o.id, - "name": name, + "name": o.name, "description": o.description, "username": o.username, "home_directory": o.home_directory, diff --git a/plugins/modules/storage_box_subaccount.py b/plugins/modules/storage_box_subaccount.py index 06476a5..014ac78 100644 --- a/plugins/modules/storage_box_subaccount.py +++ b/plugins/modules/storage_box_subaccount.py @@ -39,10 +39,6 @@ options: - Name of the Storage Box Subaccount to manage. - Required if no Storage Box Subaccount O(id) is given. - 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 password: description: @@ -225,13 +221,11 @@ hcloud_storage_box_subaccount: sample: "2025-12-03T13:47:47Z" """ -import string from ..module_utils import _storage_box, _storage_box_subaccount from ..module_utils._base import AnsibleHCloud, AnsibleModule from ..module_utils._client import client_resource_not_found 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.storage_boxes import ( BoundStorageBox, @@ -245,7 +239,6 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud): storage_box: BoundStorageBox | None = None storage_box_subaccount: BoundStorageBoxSubaccount | None = None - storage_box_subaccount_name: str | None = None def __init__(self, module: AnsibleModule): storage_box_experimental_warning(module) @@ -254,7 +247,7 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud): def _prepare_result(self): if self.storage_box_subaccount is None: 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): 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: self.storage_box_subaccount = self.storage_box.get_subaccount_by_id(value) 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 - # Get the name of the resource from the labels - if self.storage_box_subaccount is not None: - self.storage_box_subaccount_name = self.storage_box_subaccount.labels.pop(NAME_LABEL_KEY) + # Backward compatible upgrade from label based name + if self.storage_box_subaccount is None: + self.storage_box_subaccount = _storage_box_subaccount.get_by_label_name(self.storage_box, value) def _create(self): self.fail_on_invalid_params( required=["name", "home_directory", "password"], ) params = { + "name": self.module.params.get("name"), "home_directory": self.module.params.get("home_directory"), "password": self.module.params.get("password"), } @@ -287,19 +280,12 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud): if (value := self.module.params.get("access_settings")) is not None: 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: resp = self.storage_box.create_subaccount(**params) self.storage_box_subaccount = resp.subaccount resp.action.wait_until_finished() self.storage_box_subaccount.reload() - self.storage_box_subaccount_name = self.storage_box_subaccount.labels.pop(NAME_LABEL_KEY) self._mark_as_changed() @@ -324,6 +310,11 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud): self._mark_as_changed() 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.storage_box_subaccount.description: params["description"] = value @@ -334,25 +325,10 @@ class AnsibleStorageBoxSubaccount(AnsibleHCloud): params["labels"] = value 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 if params or need_reload: if not self.module.check_mode: 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): if not self.module.check_mode: @@ -439,23 +415,6 @@ def main(): module = AnsibleStorageBoxSubaccount.define_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"): case "reset_password": o.reset_password() diff --git a/plugins/modules/storage_box_subaccount_info.py b/plugins/modules/storage_box_subaccount_info.py index 4384ee0..a429142 100644 --- a/plugins/modules/storage_box_subaccount_info.py +++ b/plugins/modules/storage_box_subaccount_info.py @@ -161,7 +161,6 @@ from ansible.module_utils.basic import AnsibleModule from ..module_utils import _storage_box, _storage_box_subaccount from ..module_utils._base import AnsibleHCloud 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.storage_boxes import ( BoundStorageBox, @@ -184,11 +183,7 @@ class AnsibleStorageBoxSubaccountInfo(AnsibleHCloud): for o in self.storage_box_subaccounts or []: if o is not None: - # Workaround the missing name property - # 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)) + result.append(_storage_box_subaccount.prepare_result(o)) return result def fetch(self): @@ -201,7 +196,11 @@ class AnsibleStorageBoxSubaccountInfo(AnsibleHCloud): if (value := self.module.params.get("id")) is not None: self.storage_box_subaccounts = [self.storage_box.get_subaccount_by_id(value)] 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: params = {} if (value := self.module.params.get("label_selector")) is not None: