From 5394c6f2466d1d30193df643367918dada18be72 Mon Sep 17 00:00:00 2001 From: "Jonas L." Date: Wed, 10 Dec 2025 13:18:36 +0100 Subject: [PATCH] feat: add support for Storage Boxes (#676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ##### SUMMARY We collect all changes for the Storage Box support in this PR. It will only be merged when everything is implemented through smaller pull requests targeting the `storage-boxes` branch. --------- Co-authored-by: Julian Tölle --- changelogs/fragments/storage-boxes.yml | 52 ++ meta/runtime.yml | 21 + plugins/doc_fragments/hcloud.py | 14 +- plugins/module_utils/experimental.py | 7 + plugins/module_utils/hcloud.py | 6 + plugins/module_utils/storage_box.py | 66 ++ plugins/module_utils/storage_box_snapshot.py | 21 + .../module_utils/storage_box_subaccount.py | 44 ++ plugins/modules/storage_box.py | 623 ++++++++++++++++++ plugins/modules/storage_box_info.py | 256 +++++++ plugins/modules/storage_box_snapshot.py | 282 ++++++++ plugins/modules/storage_box_snapshot_info.py | 213 ++++++ plugins/modules/storage_box_subaccount.py | 476 +++++++++++++ .../modules/storage_box_subaccount_info.py | 242 +++++++ plugins/modules/storage_box_type_info.py | 194 ++++++ .../common/defaults/main/common.yml | 6 + .../certificate/defaults/main/common.yml | 6 + .../certificate_info/defaults/main/common.yml | 6 + .../datacenter_info/defaults/main/common.yml | 6 + .../filter_all/defaults/main/common.yml | 6 + .../targets/firewall/defaults/main/common.yml | 6 + .../firewall_info/defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../floating_ip/defaults/main/common.yml | 6 + .../floating_ip_info/defaults/main/common.yml | 6 + .../image_info/defaults/main/common.yml | 6 + .../targets/iso_info/defaults/main/common.yml | 6 + .../load_balancer/defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../location_info/defaults/main/common.yml | 6 + .../targets/network/defaults/main/common.yml | 6 + .../network_info/defaults/main/common.yml | 6 + .../placement_group/defaults/main/common.yml | 6 + .../primary_ip/defaults/main/common.yml | 6 + .../primary_ip_info/defaults/main/common.yml | 6 + .../targets/rdns/defaults/main/common.yml | 6 + .../targets/route/defaults/main/common.yml | 6 + .../targets/server/defaults/main/common.yml | 6 + .../server_info/defaults/main/common.yml | 6 + .../server_network/defaults/main/common.yml | 6 + .../server_type_info/defaults/main/common.yml | 6 + .../targets/ssh_key/defaults/main/common.yml | 6 + .../ssh_key_info/defaults/main/common.yml | 6 + tests/integration/targets/storage_box/aliases | 3 + .../storage_box/defaults/main/common.yml | 35 + .../storage_box/defaults/main/main.yml | 6 + .../targets/storage_box/meta/main.yml | 3 + .../targets/storage_box/tasks/cleanup.yml | 5 + .../targets/storage_box/tasks/main.yml | 31 + .../targets/storage_box/tasks/prepare.yml | 1 + .../targets/storage_box/tasks/test.yml | 247 +++++++ .../targets/storage_box_info/aliases | 3 + .../storage_box_info/defaults/main/common.yml | 35 + .../storage_box_info/defaults/main/main.yml | 6 + .../targets/storage_box_info/meta/main.yml | 3 + .../storage_box_info/tasks/cleanup.yml | 5 + .../targets/storage_box_info/tasks/main.yml | 31 + .../storage_box_info/tasks/prepare.yml | 15 + .../targets/storage_box_info/tasks/test.yml | 83 +++ .../targets/storage_box_snapshot/aliases | 3 + .../defaults/main/common.yml | 35 + .../defaults/main/main.yml | 6 + .../storage_box_snapshot/meta/main.yml | 3 + .../storage_box_snapshot/tasks/cleanup.yml | 5 + .../storage_box_snapshot/tasks/main.yml | 31 + .../storage_box_snapshot/tasks/prepare.yml | 15 + .../storage_box_snapshot/tasks/test.yml | 119 ++++ .../targets/storage_box_snapshot_info/aliases | 3 + .../defaults/main/common.yml | 35 + .../defaults/main/main.yml | 6 + .../storage_box_snapshot_info/meta/main.yml | 3 + .../tasks/cleanup.yml | 5 + .../storage_box_snapshot_info/tasks/main.yml | 31 + .../tasks/prepare.yml | 28 + .../storage_box_snapshot_info/tasks/test.yml | 81 +++ .../targets/storage_box_subaccount/aliases | 3 + .../defaults/main/common.yml | 35 + .../defaults/main/main.yml | 6 + .../storage_box_subaccount/meta/main.yml | 3 + .../storage_box_subaccount/tasks/cleanup.yml | 5 + .../storage_box_subaccount/tasks/main.yml | 31 + .../storage_box_subaccount/tasks/prepare.yml | 15 + .../storage_box_subaccount/tasks/test.yml | 190 ++++++ .../storage_box_subaccount_info/aliases | 3 + .../defaults/main/common.yml | 35 + .../defaults/main/main.yml | 6 + .../storage_box_subaccount_info/meta/main.yml | 3 + .../tasks/cleanup.yml | 5 + .../tasks/main.yml | 31 + .../tasks/prepare.yml | 37 ++ .../tasks/test.yml | 86 +++ .../targets/storage_box_type_info/aliases | 3 + .../defaults/main/common.yml | 35 + .../defaults/main/main.yml | 3 + .../storage_box_type_info/tasks/main.yml | 31 + .../storage_box_type_info/tasks/test.yml | 64 ++ .../subnetwork/defaults/main/common.yml | 6 + .../targets/volume/defaults/main/common.yml | 6 + .../defaults/main/common.yml | 6 + .../volume_info/defaults/main/common.yml | 6 + .../targets/zone/defaults/main/common.yml | 6 + .../zone_info/defaults/main/common.yml | 6 + .../zone_rrset/defaults/main/common.yml | 6 + .../zone_rrset_info/defaults/main/common.yml | 6 + tests/unit/conftest.py | 1 + 109 files changed, 4305 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/storage-boxes.yml create mode 100644 plugins/module_utils/storage_box.py create mode 100644 plugins/module_utils/storage_box_snapshot.py create mode 100644 plugins/module_utils/storage_box_subaccount.py create mode 100644 plugins/modules/storage_box.py create mode 100644 plugins/modules/storage_box_info.py create mode 100644 plugins/modules/storage_box_snapshot.py create mode 100644 plugins/modules/storage_box_snapshot_info.py create mode 100644 plugins/modules/storage_box_subaccount.py create mode 100644 plugins/modules/storage_box_subaccount_info.py create mode 100644 plugins/modules/storage_box_type_info.py create mode 100644 tests/integration/targets/storage_box/aliases create mode 100644 tests/integration/targets/storage_box/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box/meta/main.yml create mode 100644 tests/integration/targets/storage_box/tasks/cleanup.yml create mode 100644 tests/integration/targets/storage_box/tasks/main.yml create mode 100644 tests/integration/targets/storage_box/tasks/prepare.yml create mode 100644 tests/integration/targets/storage_box/tasks/test.yml create mode 100644 tests/integration/targets/storage_box_info/aliases create mode 100644 tests/integration/targets/storage_box_info/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box_info/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box_info/meta/main.yml create mode 100644 tests/integration/targets/storage_box_info/tasks/cleanup.yml create mode 100644 tests/integration/targets/storage_box_info/tasks/main.yml create mode 100644 tests/integration/targets/storage_box_info/tasks/prepare.yml create mode 100644 tests/integration/targets/storage_box_info/tasks/test.yml create mode 100644 tests/integration/targets/storage_box_snapshot/aliases create mode 100644 tests/integration/targets/storage_box_snapshot/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box_snapshot/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box_snapshot/meta/main.yml create mode 100644 tests/integration/targets/storage_box_snapshot/tasks/cleanup.yml create mode 100644 tests/integration/targets/storage_box_snapshot/tasks/main.yml create mode 100644 tests/integration/targets/storage_box_snapshot/tasks/prepare.yml create mode 100644 tests/integration/targets/storage_box_snapshot/tasks/test.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/aliases create mode 100644 tests/integration/targets/storage_box_snapshot_info/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/meta/main.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/tasks/cleanup.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/tasks/main.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/tasks/prepare.yml create mode 100644 tests/integration/targets/storage_box_snapshot_info/tasks/test.yml create mode 100644 tests/integration/targets/storage_box_subaccount/aliases create mode 100644 tests/integration/targets/storage_box_subaccount/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box_subaccount/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box_subaccount/meta/main.yml create mode 100644 tests/integration/targets/storage_box_subaccount/tasks/cleanup.yml create mode 100644 tests/integration/targets/storage_box_subaccount/tasks/main.yml create mode 100644 tests/integration/targets/storage_box_subaccount/tasks/prepare.yml create mode 100644 tests/integration/targets/storage_box_subaccount/tasks/test.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/aliases create mode 100644 tests/integration/targets/storage_box_subaccount_info/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/meta/main.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/tasks/cleanup.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/tasks/main.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/tasks/prepare.yml create mode 100644 tests/integration/targets/storage_box_subaccount_info/tasks/test.yml create mode 100644 tests/integration/targets/storage_box_type_info/aliases create mode 100644 tests/integration/targets/storage_box_type_info/defaults/main/common.yml create mode 100644 tests/integration/targets/storage_box_type_info/defaults/main/main.yml create mode 100644 tests/integration/targets/storage_box_type_info/tasks/main.yml create mode 100644 tests/integration/targets/storage_box_type_info/tasks/test.yml diff --git a/changelogs/fragments/storage-boxes.yml b/changelogs/fragments/storage-boxes.yml new file mode 100644 index 0000000..39645a3 --- /dev/null +++ b/changelogs/fragments/storage-boxes.yml @@ -0,0 +1,52 @@ +minor_changes: + - storage_box - New module to create and manage Storage Boxes in Hetzner. + - storage_box_info - New module to gather infos about Hetzner Storage Boxes. + - storage_box_snapshot - New module to create and manage Storage Box Snapshots in Hetzner. + - storage_box_snapshot_info - New module to gather infos about Hetzner Storage Box Snapshots. + - storage_box_subaccount - New module to create and manage Storage Box Subaccounts in Hetzner. + - storage_box_subaccount_info - New module to gather infos about Hetzner Storage Box Subaccounts. + - storage_box_type_info - New module to gather infos about Hetzner Storage Box Types. +release_summary: | + This release adds support for the new `Storage Box API`_. + + Storage Box support is **experimental**, breaking changes may occur within minor releases. + + See the `experimental tracking issue`_ for more details. + + **Examples** + + .. code:: yaml + + - name: Create a Storage Box + hetzner.hcloud.storage_box: + name: backups + storage_box_type: bx11 + location: fsn1 + password: my-secret + access_settings: + reachable_externally: true + ssh_enabled: true + state: present + + - name: Create a Storage Box Subaccount + hetzner.hcloud.storage_box_subaccount: + storage_box: backups + name: subaccount1 + home_directory: backups/subaccount1 + password: secret + access_settings: + readonly: true + labels: + env: prod + state: present + + - name: Take a Storage Box Snapshot + hetzner.hcloud.storage_box_snapshot: + storage_box: backups + description: before app migration + labels: + env: prod + state: present + + .. _Storage Box API: https://docs.hetzner.cloud/reference/hetzner#storage-boxes + .. _experimental tracking issue: https://github.com/ansible-collections/hetzner.hcloud/issues/756 diff --git a/meta/runtime.yml b/meta/runtime.yml index 0f162d4..a5da25b 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -32,6 +32,13 @@ action_groups: - server_type_info - ssh_key - ssh_key_info + - storage_box + - storage_box_info + - storage_box_snapshot + - storage_box_snapshot_info + - storage_box_subaccount + - storage_box_subaccount_info + - storage_box_type_info - subnetwork - volume - volume_info @@ -103,6 +110,20 @@ plugin_routing: redirect: hetzner.hcloud.ssh_key_info hcloud_ssh_key: redirect: hetzner.hcloud.ssh_key + hcloud_storage_box: + redirect: storage_box + hcloud_storage_box_info: + redirect: storage_box_info + hcloud_storage_box_snapshot: + redirect: storage_box_snapshot + hcloud_storage_box_snapshot_info: + redirect: storage_box_snapshot_info + hcloud_storage_box_subaccount: + redirect: storage_box_subaccount + hcloud_storage_box_subaccount_info: + redirect: storage_box_subaccount_info + hcloud_storage_box_type_info: + redirect: storage_box_type_info hcloud_subnetwork: redirect: hetzner.hcloud.subnetwork hcloud_volume_info: diff --git a/plugins/doc_fragments/hcloud.py b/plugins/doc_fragments/hcloud.py index eee4a9c..70bbd3c 100644 --- a/plugins/doc_fragments/hcloud.py +++ b/plugins/doc_fragments/hcloud.py @@ -10,24 +10,30 @@ class ModuleDocFragment: options: api_token: description: - - The API Token for the Hetzner Cloud. + - The token for the Hetzner Cloud API. - You can also set this option by using the C(HCLOUD_TOKEN) environment variable. required: True type: str api_endpoint: description: - - The API Endpoint for the Hetzner Cloud. + - The endpoint for the Hetzner Cloud API. - You can also set this option by using the C(HCLOUD_ENDPOINT) environment variable. default: https://api.hetzner.cloud/v1 type: str aliases: [endpoint] + api_endpoint_hetzner: + description: + - The endpoint for the Hetzner API. + - You can also set this option by using the C(HETZNER_ENDPOINT) environment variable. + default: https://api.hetzner.com/v1 + type: str requirements: - python-dateutil >= 2.7.5 - requests >=2.20 seealso: - - name: Documentation for Hetzner Cloud API - description: Complete reference for the Hetzner Cloud API. + - name: Documentation for Hetzner APIs + description: Complete reference for the Hetzner APIs. link: https://docs.hetzner.cloud """ diff --git a/plugins/module_utils/experimental.py b/plugins/module_utils/experimental.py index e253e87..28a9198 100644 --- a/plugins/module_utils/experimental.py +++ b/plugins/module_utils/experimental.py @@ -32,3 +32,10 @@ def experimental_warning_function(product: str, maturity: str, url: str): module.warn(message) return fn + + +storage_box_experimental_warning = experimental_warning_function( + "Storage Box support", + "experimental", + "https://github.com/ansible-collections/hetzner.hcloud/issues/756", +) diff --git a/plugins/module_utils/hcloud.py b/plugins/module_utils/hcloud.py index bc3b7ce..dc9d652 100644 --- a/plugins/module_utils/hcloud.py +++ b/plugins/module_utils/hcloud.py @@ -103,6 +103,7 @@ class AnsibleHCloud: self.client = Client( token=self.module.params["api_token"], api_endpoint=self.module.params["api_endpoint"], + api_endpoint_hetzner=self.module.params["api_endpoint_hetzner"], application_name="ansible-module", application_version=version, # Total waiting time before timeout is > 117.0 @@ -163,6 +164,11 @@ class AnsibleHCloud: "default": "https://api.hetzner.cloud/v1", "aliases": ["endpoint"], }, + "api_endpoint_hetzner": { + "type": "str", + "fallback": (env_fallback, ["HETZNER_ENDPOINT"]), + "default": "https://api.hetzner.com/v1", + }, } def _prepare_result(self) -> dict[str, Any]: diff --git a/plugins/module_utils/storage_box.py b/plugins/module_utils/storage_box.py new file mode 100644 index 0000000..5e307bc --- /dev/null +++ b/plugins/module_utils/storage_box.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from ..module_utils.client import client_resource_not_found +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBox, + StorageBoxesClient, +) + + +def get(client: StorageBoxesClient, param: str | int) -> BoundStorageBox: + """ + Get a Bound Storage Box either by ID or name. + + If the given parameter is an ID, return a partial Bound Storage Box to reduce the amount + of API requests. + """ + try: + return BoundStorageBox( + client, + data={"id": int(param)}, + complete=False, + ) + except ValueError: # param is not an id + result = client.get_by_name(param) + if result is None: + # pylint: disable=raise-missing-from + raise client_resource_not_found("storage box", param) + return result + + +def prepare_result(o: BoundStorageBox): + return { + "id": o.id, + "name": o.name, + "storage_box_type": o.storage_box_type.name, + "location": o.location.name, + "labels": o.labels, + "delete_protection": o.protection["delete"], + "access_settings": { + "reachable_externally": o.access_settings.reachable_externally, + "samba_enabled": o.access_settings.samba_enabled, + "ssh_enabled": o.access_settings.ssh_enabled, + "webdav_enabled": o.access_settings.webdav_enabled, + "zfs_enabled": o.access_settings.zfs_enabled, + }, + "username": o.username, + "server": o.server, + "system": o.system, + "status": o.status, + "stats": { + "size": o.stats.size, + "size_data": o.stats.size_data, + "size_snapshots": o.stats.size_snapshots, + }, + "snapshot_plan": ( + None + if o.snapshot_plan is None + else { + "max_snapshots": o.snapshot_plan.max_snapshots, + "hour": o.snapshot_plan.hour, + "minute": o.snapshot_plan.minute, + "day_of_week": o.snapshot_plan.day_of_week, + "day_of_month": o.snapshot_plan.day_of_month, + } + ), + } diff --git a/plugins/module_utils/storage_box_snapshot.py b/plugins/module_utils/storage_box_snapshot.py new file mode 100644 index 0000000..4fe10c1 --- /dev/null +++ b/plugins/module_utils/storage_box_snapshot.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBoxSnapshot, +) + + +def prepare_result(o: BoundStorageBoxSnapshot): + return { + "storage_box": o.storage_box.id, + "id": o.id, + "name": o.name, + "description": o.description, + "labels": o.labels, + "stats": { + "size": o.stats.size, + "size_filesystem": o.stats.size_filesystem, + }, + "is_automatic": o.is_automatic, + "created": o.created.isoformat(), + } diff --git a/plugins/module_utils/storage_box_subaccount.py b/plugins/module_utils/storage_box_subaccount.py new file mode 100644 index 0000000..b4175fd --- /dev/null +++ b/plugins/module_utils/storage_box_subaccount.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBox, + BoundStorageBoxSubaccount, +) + +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}'") + + 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}") + + +def prepare_result(o: BoundStorageBoxSubaccount, name: str): + return { + "storage_box": o.storage_box.id, + "id": o.id, + "name": name, + "description": o.description, + "username": o.username, + "home_directory": o.home_directory, + "server": o.server, + "access_settings": { + "reachable_externally": o.access_settings.reachable_externally, + "samba_enabled": o.access_settings.samba_enabled, + "ssh_enabled": o.access_settings.ssh_enabled, + "webdav_enabled": o.access_settings.webdav_enabled, + "readonly": o.access_settings.readonly, + }, + "labels": o.labels, + "created": o.created.isoformat(), + } diff --git a/plugins/modules/storage_box.py b/plugins/modules/storage_box.py new file mode 100644 index 0000000..cf8f84d --- /dev/null +++ b/plugins/modules/storage_box.py @@ -0,0 +1,623 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box + +short_description: Create and manage Storage Boxes in Hetzner. + +description: + - Create, update and delete Storage Boxes in Hetzner. + - See the L(Storage Boxes API documentation,https://docs.hetzner.cloud/reference/hetzner#storage-boxes) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + id: + description: + - ID of the Storage Box to manage. + - Required if no Storage Box O(name) is given. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - Name of the Storage Box to manage. + - Required if no Storage Box O(id) is given. + - Required if the Storage Box does not exist. + type: str + storage_box_type: + description: + - Name or ID of the Storage Box Type for the Storage Box. + - Required if the Storage Box does not exist. + type: str + aliases: [type] + location: + description: + - Name or ID of the Location for the Storage Box. + - Required if the Storage Box does not exist. + type: str + password: + description: + - Password for the Storage Box. + - Required if the Storage Box does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs) for the Storage Box. + type: dict + ssh_keys: + description: + - SSH public keys in OpenSSH format to inject into the Storage Box. + type: list + elements: str + access_settings: + description: + - Access settings of the Storage Box. + type: dict + suboptions: + reachable_externally: + description: + - Whether access from outside the Hetzner network is allowed. + type: bool + default: false + samba_enabled: + description: + - Whether the Samba subsystem is enabled. + type: bool + default: false + ssh_enabled: + description: + - Whether the SSH subsystem is enabled. + type: bool + default: false + webdav_enabled: + description: + - Whether the WebDAV subsystem is enabled. + type: bool + default: false + zfs_enabled: + description: + - Whether the ZFS snapshot folder is visible. + type: bool + default: false + snapshot_plan: + description: + - Snapshot plan of the Storage Box. + - Use null to disabled the snapshot plan. + type: dict + suboptions: + max_snapshots: + description: + - Maximum amount of Snapshots that will be created by this Snapshot Plan. + - Older Snapshots will be deleted. + type: int + required: true + hour: + description: + - Hour when the Snapshot Plan is executed (UTC). + type: int + required: true + minute: + description: + - Minute when the Snapshot Plan is executed (UTC). + type: int + required: true + day_of_week: + description: + - Day of the week when the Snapshot Plan is executed. + - Starts at 1 for Monday til 7 for Sunday. + - Null means every day. + type: int + day_of_month: + description: + - Day of the month when the Snapshot Plan is executed. + - Null means every day. + type: int + delete_protection: + description: + - Protect the Storage Box from deletion. + type: bool + snapshot: + description: + - Snapshot ID or Name to rollback to. + - Only used when O(state=rollback_snapshot) + type: str + state: + description: + - State of the Storage Box. + - C(reset_password) and C(rollback_snapshot) are not idempotent. + default: present + choices: [absent, present, reset_password, rollback_snapshot] + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a Storage Box + hetzner.hcloud.storage_box: + name: my-storage-box + storage_box_type: bx11 + location: fsn1 + password: my-secret + labels: + env: prod + state: present + +- name: Create a Storage Box with access settings + hetzner.hcloud.storage_box: + name: my-storage-box + storage_box_type: bx11 + location: fsn1 + password: my-secret + access_settings: + reachable_externally: true + ssh_enabled: true + samba_enabled: false + webdav_enabled: false + zfs_enabled: false + state: present + +- name: Create a Storage Box with snapshot plan + hetzner.hcloud.storage_box: + name: my-storage-box + storage_box_type: bx11 + location: fsn1 + password: my-secret + snapshot_plan: + max_snapshots: 10 + hour: 3 + minute: 30 + state: present + +- name: Disable a Storage Box snapshot plan + hetzner.hcloud.storage_box: + name: my-storage-box + snapshot_plan: null + state: present + +- name: Reset a Storage Box password + hetzner.hcloud.storage_box: + name: my-storage-box + password: my-secret + state: reset_password + +- name: Rollback a Storage Box to a Snapshot + hetzner.hcloud.storage_box: + name: my-storage-box + snapshot: 2025-12-03T13-47-47 + state: rollback_snapshot + +- name: Delete a Storage Box + hetzner.hcloud.storage_box: + name: my-storage-box + state: absent +""" + +RETURN = """ +hcloud_storage_box: + description: Details about the Storage Box. + returned: always + type: dict + contains: + id: + description: ID of the Storage Box. + returned: always + type: int + sample: 1937415 + name: + description: Name of the Storage Box. + returned: always + type: str + sample: my-storage-box + storage_box_type: + description: Name of the Storage Box Type. + returned: always + type: str + sample: bx11 + location: + description: Name of the Location of the Storage Box. + returned: always + type: str + sample: fsn1 + labels: + description: User-defined labels (key-value pairs) of the Storage Box. + returned: always + type: dict + sample: + env: prod + delete_protection: + description: Protect the Storage Box from deletion. + returned: always + type: bool + sample: false + access_settings: + description: Access settings of the Storage Box. + returned: always + type: dict + contains: + reachable_externally: + description: Whether access from outside the Hetzner network is allowed. + returned: always + type: bool + sample: false + samba_enabled: + description: Whether the Samba subsystem is enabled. + returned: always + type: bool + sample: false + ssh_enabled: + description: Whether the SSH subsystem is enabled. + returned: always + type: bool + sample: true + webdav_enabled: + description: Whether the WebDAV subsystem is enabled. + returned: always + type: bool + sample: false + zfs_enabled: + description: Whether the ZFS snapshot folder is visible. + returned: always + type: bool + sample: false + snapshot_plan: + description: Snapshot plan of the Storage Box. + returned: when enabled + type: dict + contains: + max_snapshots: + description: Maximum amount of Snapshots that will be created by this Snapshot Plan. + returned: always + type: int + sample: 10 + hour: + description: Hour when the Snapshot Plan is executed (UTC). + returned: always + type: int + sample: 3 + minute: + description: Minute when the Snapshot Plan is executed (UTC). + returned: always + type: int + sample: 30 + day_of_week: + description: Day of the week when the Snapshot Plan is executed. Null means every day. + returned: always + type: int + sample: 1 + day_of_month: + description: Day of the month when the Snapshot Plan is executed. Null means every day. + returned: always + type: int + sample: 30 + username: + description: User name of the Storage Box. + returned: always + type: str + sample: u505337 + server: + description: FQDN of the Storage Box. + returned: always + type: str + sample: u505337.your-storagebox.de + system: + description: Host system of the Storage Box. + returned: always + type: str + sample: HEL1-BX136 + status: + description: Status of the Storage Box. + returned: always + type: str + sample: active + stats: + description: Statistics of the Storage Box. + returned: always + type: dict + contains: + size: + description: Current disk usage in bytes. + returned: always + type: int + sample: 10485760 + size_data: + description: Current disk usage for data in bytes. + returned: always + type: int + sample: 10485760 + size_snapshots: + description: Current disk usage for snapshots in bytes. + returned: always + type: int + sample: 10485760 +""" + +from ..module_utils import storage_box +from ..module_utils.client import client_resource_not_found +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud, AnsibleModule +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.locations import Location +from ..module_utils.vendor.hcloud.storage_box_types import StorageBoxType +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBox, + StorageBoxAccessSettings, + StorageBoxSnapshot, + StorageBoxSnapshotPlan, +) + + +class AnsibleStorageBox(AnsibleHCloud): + represent = "storage_box" + + storage_box: BoundStorageBox | None = None + + def __init__(self, module: AnsibleModule): + storage_box_experimental_warning(module) + super().__init__(module) + + def _prepare_result(self): + if self.storage_box is not None: + return storage_box.prepare_result(self.storage_box) + return {} + + def _fetch(self): + if self.module.params.get("id") is not None: + self.storage_box = self.client.storage_boxes.get_by_id(self.module.params.get("id")) + else: + self.storage_box = self.client.storage_boxes.get_by_name(self.module.params.get("name")) + + def _create(self): + self.fail_on_invalid_params( + required=["name", "storage_box_type", "location", "password"], + ) + params = { + "name": self.module.params.get("name"), + "storage_box_type": StorageBoxType(self.module.params.get("storage_box_type")), + "location": Location(self.module.params.get("location")), + "password": self.module.params.get("password"), + } + + if (value := self.module.params.get("labels")) is not None: + params["labels"] = value + + if (value := self.module.params.get("access_settings")) is not None: + params["access_settings"] = StorageBoxAccessSettings.from_dict(value) + + if (value := self.module.params.get("ssh_keys")) is not None: + params["ssh_keys"] = value + + if not self.module.check_mode: + resp = self.client.storage_boxes.create(**params) + self.storage_box = resp.storage_box + resp.action.wait_until_finished() + + if (value := self.module.params.get("delete_protection")) is not None: + if not self.module.check_mode: + action = self.storage_box.change_protection(delete=value) + action.wait_until_finished() + + if self.module.param_is_defined("snapshot_plan"): + if (value := self.module.params.get("snapshot_plan")) is not None: + if not self.module.check_mode: + action = self.storage_box.enable_snapshot_plan(StorageBoxSnapshotPlan.from_dict(value)) + action.wait_until_finished() + + if not self.module.check_mode: + self.storage_box.reload() + + self._mark_as_changed() + + def _update(self): + need_reload = False + + if (value := self.module.params.get("storage_box_type")) is not None: + if not self.storage_box.storage_box_type.has_id_or_name(value): + if not self.module.check_mode: + action = self.storage_box.change_type(StorageBoxType(value)) + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + + if (value := self.module.params.get("access_settings")) is not None: + access_settings = StorageBoxAccessSettings.from_dict(value) + if self.storage_box.access_settings.to_payload() != access_settings.to_payload(): + if not self.module.check_mode: + action = self.storage_box.update_access_settings(access_settings) + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + + if (value := self.module.params.get("delete_protection")) is not None: + if self.storage_box.protection["delete"] != value: + if not self.module.check_mode: + action = self.storage_box.change_protection(delete=value) + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + + if self.module.param_is_defined("snapshot_plan"): + if (value := self.module.params.get("snapshot_plan")) is not None: + snapshot_plan = StorageBoxSnapshotPlan.from_dict(value) + if ( + self.storage_box.snapshot_plan is None + or self.storage_box.snapshot_plan.to_payload() != snapshot_plan.to_payload() + ): + if not self.module.check_mode: + action = self.storage_box.enable_snapshot_plan(snapshot_plan) + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + else: + if self.storage_box.snapshot_plan is not None: + if not self.module.check_mode: + action = self.storage_box.disable_snapshot_plan() + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + + params = {} + if (value := self.module.params.get("name")) is not None and value != self.storage_box.name: + self.fail_on_invalid_params(required=["id"]) + params["name"] = value + self._mark_as_changed() + + if (value := self.module.params.get("labels")) is not None and value != self.storage_box.labels: + params["labels"] = value + self._mark_as_changed() + + # Update only if params holds changes or the data must be refreshed (actions + # were triggered) + if params or need_reload: + if not self.module.check_mode: + self.storage_box = self.storage_box.update(**params) + + def _delete(self): + if not self.module.check_mode: + resp = self.storage_box.delete() + resp.action.wait_until_finished() + + self.storage_box = None + self._mark_as_changed() + + def present(self): + try: + self._fetch() + if self.storage_box is None: + self._create() + else: + self._update() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def absent(self): + try: + self._fetch() + if self.storage_box is None: + return + self._delete() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def reset_password(self): + self.fail_on_invalid_params( + required=["password"], + ) + try: + self._fetch() + if self.storage_box is None: + raise client_resource_not_found( + "storage box", + self.module.params.get("id") or self.module.params.get("name"), + ) + if not self.module.check_mode: + action = self.storage_box.reset_password(self.module.params.get("password")) + action.wait_until_finished() + + self._mark_as_changed() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def rollback_snapshot(self): + self.fail_on_invalid_params( + required=["snapshot"], + ) + try: + self._fetch() + if self.storage_box is None: + raise client_resource_not_found( + "storage box", + self.module.params.get("id") or self.module.params.get("name"), + ) + + if not self.module.check_mode: + action = self.storage_box.rollback_snapshot(StorageBoxSnapshot(self.module.params.get("snapshot"))) + action.wait_until_finished() + + self._mark_as_changed() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + storage_box_type={"type": "str", "aliases": ["type"]}, + location={"type": "str"}, + password={"type": "str", "no_log": True}, + ssh_keys={"type": "list", "elements": "str", "no_log": False}, + labels={"type": "dict"}, + access_settings={ + "type": "dict", + "options": dict( + reachable_externally={"type": "bool", "default": False}, + samba_enabled={"type": "bool", "default": False}, + ssh_enabled={"type": "bool", "default": False}, + webdav_enabled={"type": "bool", "default": False}, + zfs_enabled={"type": "bool", "default": False}, + ), + }, + snapshot_plan={ + "type": "dict", + "options": dict( + max_snapshots={"type": "int", "required": True}, + hour={"type": "int", "required": True}, + minute={"type": "int", "required": True}, + day_of_week={"type": "int"}, + day_of_month={"type": "int"}, + ), + }, + delete_protection={"type": "bool"}, + snapshot={"type": "str"}, + state={ + "choices": ["absent", "present", "reset_password", "rollback_snapshot"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleStorageBox.define_module() + o = AnsibleStorageBox(module) + + match module.params.get("state"): + case "reset_password": + o.reset_password() + case "rollback_snapshot": + o.rollback_snapshot() + case "absent": + o.absent() + case _: + o.present() + + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/storage_box_info.py b/plugins/modules/storage_box_info.py new file mode 100644 index 0000000..ac8dd4a --- /dev/null +++ b/plugins/modules/storage_box_info.py @@ -0,0 +1,256 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box_info + +short_description: Gather infos about Hetzner Storage Boxes. + +description: + - Gather infos about Hetzner Storage Boxes. + - See the L(Storage Boxes API documentation,https://docs.hetzner.cloud/reference/hetzner#storage-boxes) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + id: + description: + - ID of the Storage Box to get. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - Name of the Storage Box to get. + type: str + label_selector: + description: + - Label selector to filter the Storage Boxes to get. + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather all Storage Boxes + hetzner.hcloud.storage_box_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_info + +- name: Gather Storage Boxes by label + hetzner.hcloud.storage_box_info: + label_selector: env=prod + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_info + +- name: Gather a Storage Box by name + hetzner.hcloud.storage_box_info: + name: backups + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_info[0] + +- name: Gather a Storage Box by id + hetzner.hcloud.storage_box_info: + name: 12345 + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_info[0] +""" + +RETURN = """ +hcloud_storage_box_info: + description: List of Storage Boxes. + returned: always + type: list + elements: dict + contains: + id: + description: ID of the Storage Box. + returned: always + type: int + sample: 1937415 + name: + description: Name of the Storage Box. + returned: always + type: str + sample: my-storage-box + storage_box_type: + description: Name of the Storage Box Type. + returned: always + type: str + sample: bx11 + location: + description: Name of the Location of the Storage Box. + returned: always + type: str + sample: fsn1 + labels: + description: User-defined labels (key-value pairs) of the Storage Box. + returned: always + type: dict + sample: + env: prod + delete_protection: + description: Protect the Storage Box from deletion. + returned: always + type: bool + sample: false + access_settings: + description: Access settings of the Storage Box. + returned: always + type: dict + contains: + reachable_externally: + description: Whether access from outside the Hetzner network is allowed. + returned: always + type: bool + sample: false + samba_enabled: + description: Whether the Samba subsystem is enabled. + returned: always + type: bool + sample: false + ssh_enabled: + description: Whether the SSH subsystem is enabled. + returned: always + type: bool + sample: true + webdav_enabled: + description: Whether the WebDAV subsystem is enabled. + returned: always + type: bool + sample: false + zfs_enabled: + description: Whether the ZFS snapshot folder is visible. + returned: always + type: bool + sample: false + username: + description: User name of the Storage Box. + returned: always + type: str + sample: u505337 + server: + description: FQDN of the Storage Box. + returned: always + type: str + sample: u505337.your-storagebox.de + system: + description: Host system of the Storage Box. + returned: always + type: str + sample: HEL1-BX136 + status: + description: Status of the Storage Box. + returned: always + type: str + sample: active + stats: + description: Statistics of the Storage Box. + returned: always + type: dict + contains: + size: + description: Current disk usage in bytes. + returned: always + type: int + sample: 10485760 + size_data: + description: Current disk usage for data in bytes. + returned: always + type: int + sample: 10485760 + size_snapshots: + description: Current disk usage for snapshots in bytes. + returned: always + type: int + sample: 10485760 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import storage_box +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBox, +) + + +class AnsibleStorageBox(AnsibleHCloud): + represent = "storage_box" + + storage_box: list[BoundStorageBox] | None = None + + def __init__(self, module: AnsibleModule): + storage_box_experimental_warning(module) + super().__init__(module) + + def _prepare_result(self): + result = [] + for o in self.storage_box or []: + if o is not None: + result.append(storage_box.prepare_result(o)) + + return result + + def fetch(self): + try: + if (id_ := self.module.params.get("id")) is not None: + self.storage_box = [self.client.storage_boxes.get_by_id(id_)] + elif (name := self.module.params.get("name")) is not None: + self.storage_box = [self.client.storage_boxes.get_by_name(name)] + else: + params = {} + if (label_selector := self.module.params.get("label_selector")) is not None: + params["label_selector"] = label_selector + self.storage_box = self.client.storage_boxes.get_all(**params) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleStorageBox.define_module() + o = AnsibleStorageBox(module) + + o.fetch() + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box_info"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/storage_box_snapshot.py b/plugins/modules/storage_box_snapshot.py new file mode 100644 index 0000000..c470fbc --- /dev/null +++ b/plugins/modules/storage_box_snapshot.py @@ -0,0 +1,282 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box_snapshot + +short_description: Create and manage Storage Box Snapshots in Hetzner. + +description: + - Create, update and delete Storage Box Snapshots in Hetzner. + - See the L(Storage Box Snapshots API documentation,https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + storage_box: + description: + - ID or Name of the parent Storage Box. + - Using the ID is preferred, to reduce the amount of API requests. + type: str + required: true + id: + description: + - ID of the Storage Box Snapshot to manage. + - Required when updating or deleting and if no Storage Box Snapshot O(name) is given. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - Name of the Storage Box Snapshot to manage. + - Storage Box Snapshot names are defined by the API and cannot be changed. + - Required when updating or deleting and if no Storage Box Snapshot O(id) is given. + type: str + description: + description: + - Description of the Storage Box Snapshot. + type: str + labels: + description: + - User-defined labels (key-value pairs) for the Storage Box Snapshot. + type: dict + state: + description: + - State of the Storage Box Snapshot. + default: present + choices: [absent, present] + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a Storage Box Snapshot + hetzner.hcloud.storage_box_snapshot: + storage_box: my-storage-box + description: before app migration + labels: + env: prod + state: present + +- name: Delete a Storage Box Snapshot by name + hetzner.hcloud.storage_box_snapshot: + storage_box: my-storage-box + name: 2025-12-03T13-47-47 + state: absent + +- name: Delete a Storage Box Snapshot by id + hetzner.hcloud.storage_box_snapshot: + storage_box: 497436 + id: 405920 + state: absent +""" + +RETURN = """ +hcloud_storage_box_snapshot: + description: Details about the Storage Box Snapshot. + returned: always + type: dict + contains: + storage_box: + description: ID of the parent Storage Box. + returned: always + type: int + sample: 497436 + id: + description: ID of the Storage Box Snapshot. + returned: always + type: int + sample: 405920 + name: + description: Name of the Storage Box Snapshot. + returned: always + type: str + sample: 2025-02-12T11-35-19 + description: + description: Description of the Storage Box Snapshot. + returned: always + type: str + sample: before app migration + labels: + description: User-defined labels (key-value pairs) of the Storage Box Snapshot. + returned: always + type: dict + sample: + env: prod + stats: + description: Statistics of the Storage Box Snapshot. + returned: always + type: dict + contains: + size: + description: Current storage requirements of the Snapshot in bytes. + returned: always + type: int + sample: 10485760 + size_filesystem: + description: Size of the compressed file system contained in the Snapshot in bytes. + returned: always + type: int + sample: 10485760 + is_automatic: + description: Whether the Storage Box Snapshot was created automatically. + returned: always + type: bool + sample: false + created: + description: Point in time when the Storage Box Snapshot was created (in RFC3339 format). + returned: always + type: str + sample: "2025-12-03T13:47:47Z" +""" + +from ..module_utils import storage_box, storage_box_snapshot +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud, AnsibleModule +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBox, + BoundStorageBoxSnapshot, +) + + +class AnsibleStorageBoxSnapshot(AnsibleHCloud): + represent = "storage_box_snapshot" + + storage_box: BoundStorageBox | None = None + storage_box_snapshot: BoundStorageBoxSnapshot | None = None + + def __init__(self, module: AnsibleModule): + storage_box_experimental_warning(module) + super().__init__(module) + + def _prepare_result(self): + if self.storage_box_snapshot is None: + return {} + return storage_box_snapshot.prepare_result(self.storage_box_snapshot) + + def _fetch(self): + self.storage_box = storage_box.get(self.client.storage_boxes, self.module.params.get("storage_box")) + + if (value := self.module.params.get("id")) is not None: + self.storage_box_snapshot = self.storage_box.get_snapshot_by_id(value) + elif (value := self.module.params.get("name")) is not None: + self.storage_box_snapshot = self.storage_box.get_snapshot_by_name(value) + + def _create(self): + params = {} + + if (value := self.module.params.get("description")) is not None: + params["description"] = value + + if (value := self.module.params.get("labels")) is not None: + params["labels"] = value + + if not self.module.check_mode: + resp = self.storage_box.create_snapshot(**params) + self.storage_box_snapshot = resp.snapshot + resp.action.wait_until_finished() + + self.storage_box_snapshot.reload() + + self._mark_as_changed() + + def _update(self): + self.fail_on_invalid_params( + required_one_of=[["id", "name"]], + ) + + params = {} + if (value := self.module.params.get("description")) is not None: + if value != self.storage_box_snapshot.description: + params["description"] = value + self._mark_as_changed() + + if (value := self.module.params.get("labels")) is not None: + if value != self.storage_box_snapshot.labels: + params["labels"] = value + self._mark_as_changed() + + # Update only if params holds changes + if params: + if not self.module.check_mode: + self.storage_box_snapshot = self.storage_box_snapshot.update(**params) + + def _delete(self): + if not self.module.check_mode: + resp = self.storage_box_snapshot.delete() + resp.action.wait_until_finished() + + self.storage_box_snapshot = None + self._mark_as_changed() + + def present(self): + try: + self._fetch() + if self.storage_box_snapshot is None: + self._create() + else: + self._update() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def absent(self): + try: + self._fetch() + if self.storage_box_snapshot is None: + return + self._delete() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + storage_box={"type": "str", "required": True}, + id={"type": "int"}, + name={"type": "str"}, + description={"type": "str"}, + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleStorageBoxSnapshot.define_module() + o = AnsibleStorageBoxSnapshot(module) + + match module.params.get("state"): + case "absent": + o.absent() + case _: + o.present() + + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box_snapshot"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/storage_box_snapshot_info.py b/plugins/modules/storage_box_snapshot_info.py new file mode 100644 index 0000000..f12cd29 --- /dev/null +++ b/plugins/modules/storage_box_snapshot_info.py @@ -0,0 +1,213 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box_snapshot_info + +short_description: Gather infos about Hetzner Storage Box Snapshots. + +description: + - Gather infos about Hetzner Storage Box Snapshots. + - See the L(Storage Boxes API documentation,https://docs.hetzner.cloud/reference/hetzner#storage-boxes) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + storage_box: + description: + - ID or Name of the parent Storage Box. + - Using the ID is preferred, to reduce the amount of API requests. + type: str + required: true + id: + description: + - ID of the Storage Box Snapshot to get. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - Name of the Storage Box Snapshot to get. + type: str + label_selector: + description: + - Label selector to filter the Storage Box Snapshots to get. + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather all Storage Box Snapshots + hetzner.hcloud.storage_box_snapshot_info: + storage_box: my-storage-box + register: output + +- name: Gather Storage Box Snapshots by label + hetzner.hcloud.storage_box_snapshot_info: + storage_box: my-storage-box + label_selector: key=value + register: output + +- name: Gather Storage Box Snapshot by id + hetzner.hcloud.storage_box_snapshot_info: + storage_box: 497436 + id: 405920 + register: output + +- name: Gather Storage Box Snapshot by name + hetzner.hcloud.storage_box_snapshot_info: + storage_box: my-storage-box + name: 2025-02-12T11-35-19 + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_snapshot_info +""" + +RETURN = """ +hcloud_storage_box_snapshot_info: + description: List of Storage Box Snapshots. + returned: always + type: list + contains: + storage_box: + description: ID of the parent Storage Box. + returned: always + type: int + sample: 497436 + id: + description: ID of the Storage Box Snapshot. + returned: always + type: int + sample: 405920 + name: + description: Name of the Storage Box Snapshot. + returned: always + type: str + sample: 2025-02-12T11-35-19 + description: + description: Description of the Storage Box Snapshot. + returned: always + type: str + sample: before app migration + labels: + description: User-defined labels (key-value pairs) of the Storage Box Snapshot. + returned: always + type: dict + sample: + env: prod + stats: + description: Statistics of the Storage Box Snapshot. + returned: always + type: dict + contains: + size: + description: Current storage requirements of the Snapshot in bytes. + returned: always + type: int + sample: 10485760 + size_filesystem: + description: Size of the compressed file system contained in the Snapshot in bytes. + returned: always + type: int + sample: 10485760 + is_automatic: + description: Whether the Storage Box Snapshot was created automatically. + returned: always + type: bool + sample: false + created: + description: Point in time when the Storage Box Snapshot was created (in RFC3339 format). + returned: always + type: str + sample: "2025-12-03T13:47:47Z" +""" + +from ..module_utils import storage_box, storage_box_snapshot +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud, AnsibleModule +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.storage_boxes import ( + BoundStorageBox, + BoundStorageBoxSnapshot, +) + + +class AnsibleStorageBoxSnapshotInfo(AnsibleHCloud): + represent = "storage_box_snapshots" + + storage_box: BoundStorageBox | None = None + storage_box_snapshots: list[BoundStorageBoxSnapshot] | None = None + + def __init__(self, module: AnsibleModule): + storage_box_experimental_warning(module) + super().__init__(module) + + def _prepare_result(self): + result = [] + + for o in self.storage_box_snapshots or []: + if o is not None: + result.append(storage_box_snapshot.prepare_result(o)) + return result + + def fetch(self): + try: + self.storage_box = storage_box.get( + self.client.storage_boxes, + self.module.params.get("storage_box"), + ) + + if (id_ := self.module.params.get("id")) is not None: + self.storage_box_snapshots = [self.storage_box.get_snapshot_by_id(id_)] + elif (name := self.module.params.get("name")) is not None: + self.storage_box_snapshots = [self.storage_box.get_snapshot_by_name(name)] + else: + params = {} + if (value := self.module.params.get("label_selector")) is not None: + params["label_selector"] = value + self.storage_box_snapshots = self.storage_box.get_snapshot_all(**params) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + storage_box={"type": "str", "required": True}, + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleStorageBoxSnapshotInfo.define_module() + o = AnsibleStorageBoxSnapshotInfo(module) + + o.fetch() + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box_snapshot_info"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/storage_box_subaccount.py b/plugins/modules/storage_box_subaccount.py new file mode 100644 index 0000000..930bd1d --- /dev/null +++ b/plugins/modules/storage_box_subaccount.py @@ -0,0 +1,476 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box_subaccount + +short_description: Create and manage Storage Box Subaccounts in Hetzner. + +description: + - Create, update and delete Storage Box Subaccounts in Hetzner. + - See the L(Storage Box Subaccounts API documentation,https://docs.hetzner.cloud/reference/hetzner#storage-box-subaccounts) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + storage_box: + description: + - ID or Name of the parent Storage Box. + - Using the ID is preferred, to reduce the amount of API requests. + type: str + required: true + id: + description: + - ID of the Storage Box Subaccount to manage. + - Required if no Storage Box Subaccount O(name) is given. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - 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: + - Password for the Storage Box Subaccount. + - Required if the Storage Box Subaccount does not exist or when O(state=reset_password). + type: str + home_directory: + description: + - Home directory of the Storage Box Subaccount. + - Required if the Storage Box Subaccount does not exist. + type: str + access_settings: + description: + - Access settings of the Storage Box Subaccount. + type: dict + suboptions: + reachable_externally: + description: + - Whether access from outside the Hetzner network is allowed. + type: bool + default: false + samba_enabled: + description: + - Whether the Samba subsystem is enabled. + type: bool + default: false + ssh_enabled: + description: + - Whether the SSH subsystem is enabled. + type: bool + default: false + webdav_enabled: + description: + - Whether the WebDAV subsystem is enabled. + type: bool + default: false + readonly: + description: + - Whether the Subaccount is read-only. + type: bool + default: false + description: + description: + - Description of the Storage Box Subaccount. + type: str + labels: + description: + - User-defined labels (key-value pairs) for the Storage Box Subaccount. + type: dict + state: + description: + - State of the Storage Box Subaccount. + - C(reset_password) is not idempotent. + default: present + choices: [absent, present, reset_password] + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a Storage Box Subaccount + hetzner.hcloud.storage_box_subaccount: + storage_box: my-storage-box + name: subaccount1 + home_directory: backups/subaccount1 + password: secret + access_settings: + reachable_externally: false + ssh_enabled: true + samba_enabled: false + webdav_enabled: false + readonly: false + labels: + env: prod + state: present + +- name: Reset a Storage Box Subaccount password + hetzner.hcloud.storage_box_subaccount: + storage_box: my-storage-box + name: subaccount1 + password: secret + state: reset_password + +- name: Delete a Storage Box Subaccount by name + hetzner.hcloud.storage_box_subaccount: + storage_box: my-storage-box + name: subaccount1 + state: absent + +- name: Delete a Storage Box Subaccount by id + hetzner.hcloud.storage_box_subaccount: + storage_box: 497436 + id: 158045 + state: absent +""" + +RETURN = """ +hcloud_storage_box_subaccount: + description: Details about the Storage Box Subaccount. + returned: always + type: dict + contains: + storage_box: + description: ID of the parent Storage Box. + returned: always + type: int + sample: 497436 + id: + description: ID of the Storage Box Subaccount. + returned: always + type: int + sample: 158045 + name: + description: Name of the Storage Box Subaccount. + returned: always + type: str + sample: subaccount1 + description: + description: Description of the Storage Box Subaccount. + returned: always + type: str + sample: backups from subaccount1 + home_directory: + description: Home directory of the Storage Box Subaccount. + returned: always + type: str + sample: backups/subaccount1 + username: + description: Username of the Storage Box Subaccount. + returned: always + type: str + sample: u514605-sub1 + server: + description: FQDN of the Storage Box Subaccount. + returned: always + type: str + sample: u514605-sub1.your-storagebox.de + access_settings: + description: Access settings of the Storage Box Subaccount. + returned: always + type: dict + contains: + reachable_externally: + description: Whether access from outside the Hetzner network is allowed. + returned: always + type: bool + sample: false + samba_enabled: + description: Whether the Samba subsystem is enabled. + returned: always + type: bool + sample: false + ssh_enabled: + description: Whether the SSH subsystem is enabled. + returned: always + type: bool + sample: true + webdav_enabled: + description: Whether the WebDAV subsystem is enabled. + returned: always + type: bool + sample: false + readonly: + description: Whether the Subaccount is read-only. + returned: always + type: bool + sample: false + labels: + description: User-defined labels (key-value pairs) of the Storage Box Subaccount. + returned: always + type: dict + sample: + env: prod + created: + description: Point in time when the Storage Box Subaccount was created (in RFC3339 format). + returned: always + type: str + sample: "2025-12-03T13:47:47Z" +""" + +import string + +from ..module_utils import storage_box, storage_box_subaccount +from ..module_utils.client import client_resource_not_found +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud, AnsibleModule +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, + BoundStorageBoxSubaccount, + StorageBoxSubaccountAccessSettings, +) + + +class AnsibleStorageBoxSubaccount(AnsibleHCloud): + represent = "storage_box_subaccount" + + 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) + super().__init__(module) + + 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) + + def _fetch(self): + self.storage_box = storage_box.get(self.client.storage_boxes, self.module.params.get("storage_box")) + + 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) + + # 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) + + def _create(self): + self.fail_on_invalid_params( + required=["name", "home_directory", "password"], + ) + params = { + "home_directory": self.module.params.get("home_directory"), + "password": self.module.params.get("password"), + } + + if (value := self.module.params.get("description")) is not None: + params["description"] = value + + if (value := self.module.params.get("labels")) is not None: + params["labels"] = value + + 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() + + def _update(self): + need_reload = False + + if (value := self.module.params.get("home_directory")) is not None: + if self.storage_box_subaccount.home_directory != value: + if not self.module.check_mode: + action = self.storage_box_subaccount.change_home_directory(value) + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + + if (value := self.module.params.get("access_settings")) is not None: + access_settings = StorageBoxSubaccountAccessSettings.from_dict(value) + if self.storage_box_subaccount.access_settings.to_payload() != access_settings.to_payload(): + if not self.module.check_mode: + action = self.storage_box_subaccount.update_access_settings(access_settings) + action.wait_until_finished() + need_reload = True + self._mark_as_changed() + + params = {} + if (value := self.module.params.get("description")) is not None: + if value != self.storage_box_subaccount.description: + params["description"] = value + self._mark_as_changed() + + if (value := self.module.params.get("labels")) is not None: + if value != self.storage_box_subaccount.labels: + 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: + resp = self.storage_box_subaccount.delete() + resp.action.wait_until_finished() + + self.storage_box_subaccount = None + self._mark_as_changed() + + def present(self): + try: + self._fetch() + if self.storage_box_subaccount is None: + self._create() + else: + self._update() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def absent(self): + try: + self._fetch() + if self.storage_box_subaccount is None: + return + self._delete() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def reset_password(self): + self.fail_on_invalid_params( + required=["password"], + ) + try: + self._fetch() + if self.storage_box_subaccount is None: + raise client_resource_not_found( + "storage box", + self.module.params.get("id") or self.module.params.get("name"), + ) + if not self.module.check_mode: + action = self.storage_box_subaccount.reset_password(self.module.params.get("password")) + action.wait_until_finished() + + self._mark_as_changed() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + storage_box={"type": "str", "required": True}, + id={"type": "int"}, + name={"type": "str"}, + home_directory={"type": "str"}, + password={"type": "str", "no_log": True}, + description={"type": "str"}, + labels={"type": "dict"}, + access_settings={ + "type": "dict", + "options": dict( + reachable_externally={"type": "bool", "default": False}, + samba_enabled={"type": "bool", "default": False}, + ssh_enabled={"type": "bool", "default": False}, + webdav_enabled={"type": "bool", "default": False}, + readonly={"type": "bool", "default": False}, + ), + }, + state={ + "choices": ["absent", "present", "reset_password"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + supports_check_mode=True, + ) + + +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() + case "absent": + o.absent() + case _: + o.present() + + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box_subaccount"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/storage_box_subaccount_info.py b/plugins/modules/storage_box_subaccount_info.py new file mode 100644 index 0000000..b821946 --- /dev/null +++ b/plugins/modules/storage_box_subaccount_info.py @@ -0,0 +1,242 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box_subaccount_info + +short_description: Gather infos about Hetzner Storage Box Subaccounts. + +description: + - Gather infos about Hetzner Storage Box Subaccounts. + - See the L(Storage Box Subaccounts API documentation,https://docs.hetzner.cloud/reference/hetzner#storage-box-subaccounts) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + storage_box: + description: + - ID or Name of the parent Storage Box. + - Using the ID is preferred, to reduce the amount of API requests. + type: str + required: true + id: + description: + - ID of the Storage Box Subaccount to get. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - Name of the Storage Box Subaccount to get. + type: str + label_selector: + description: + - Label selector to filter the Storage Box Subaccounts to get. + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather all Storage Box Subaccounts + hetzner.hcloud.storage_box_subaccount_info: + register: output + +- name: Gather Storage Box Subaccounts by label + hetzner.hcloud.storage_box_subaccount_info: + label_selector: env=prod + register: output + +- name: Gather a Storage Box Subaccount by name + hetzner.hcloud.storage_box_subaccount_info: + name: subaccount1 + register: output + +- name: Gather a Storage Box Subaccount by id + hetzner.hcloud.storage_box_subaccount_info: + name: 12345 + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_subaccount_info +""" + +RETURN = """ +hcloud_storage_box_subaccount_info: + description: List of Storage Box Subaccounts. + returned: always + type: list + elements: dict + contains: + storage_box: + description: ID of the parent Storage Box. + returned: always + type: int + sample: 514605 + id: + description: ID of the Storage Box Subaccount. + returned: always + type: int + sample: 158045 + name: + description: Name of the Storage Box Subaccount. + returned: always + type: str + sample: subaccount1 + description: + description: Description of the Storage Box Subaccount. + returned: always + type: str + sample: backups from subaccount1 + home_directory: + description: Home directory of the Storage Box Subaccount. + returned: always + type: str + sample: backups/subaccount1 + username: + description: Username of the Storage Box Subaccount. + returned: always + type: str + sample: u514605-sub1 + server: + description: FQDN of the Storage Box Subaccount. + returned: always + type: str + sample: u514605-sub1.your-storagebox.de + access_settings: + description: Access settings of the Storage Box Subaccount. + returned: always + type: dict + contains: + reachable_externally: + description: Whether access from outside the Hetzner network is allowed. + returned: always + type: bool + sample: false + samba_enabled: + description: Whether the Samba subsystem is enabled. + returned: always + type: bool + sample: false + ssh_enabled: + description: Whether the SSH subsystem is enabled. + returned: always + type: bool + sample: true + webdav_enabled: + description: Whether the WebDAV subsystem is enabled. + returned: always + type: bool + sample: false + readonly: + description: Whether the Subaccount is read-only. + returned: always + type: bool + sample: false + labels: + description: User-defined labels (key-value pairs) of the Storage Box Subaccount. + returned: always + type: dict + sample: + env: prod + created: + description: Point in time when the Storage Box Subaccount was created (in RFC3339 format). + returned: always + type: str + sample: "2025-12-03T13:47:47Z" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import storage_box, storage_box_subaccount +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud +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, + BoundStorageBoxSubaccount, +) + + +class AnsibleStorageBoxSubaccountInfo(AnsibleHCloud): + represent = "storage_box_subaccounts" + + storage_box: BoundStorageBox | None = None + storage_box_subaccounts: list[BoundStorageBoxSubaccount] | None = None + + def __init__(self, module: AnsibleModule): + storage_box_experimental_warning(module) + super().__init__(module) + + def _prepare_result(self): + result = [] + + 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)) + return result + + def fetch(self): + try: + self.storage_box = storage_box.get( + self.client.storage_boxes, + self.module.params.get("storage_box"), + ) + + 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)] + else: + params = {} + if (value := self.module.params.get("label_selector")) is not None: + params["label_selector"] = value + self.storage_box_subaccounts = self.storage_box.get_subaccount_all(**params) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + storage_box={"type": "str", "required": True}, + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleStorageBoxSubaccountInfo.define_module() + o = AnsibleStorageBoxSubaccountInfo(module) + + o.fetch() + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box_subaccount_info"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/storage_box_type_info.py b/plugins/modules/storage_box_type_info.py new file mode 100644 index 0000000..1a1e78c --- /dev/null +++ b/plugins/modules/storage_box_type_info.py @@ -0,0 +1,194 @@ +#!/usr/bin/python + +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: storage_box_type_info + +short_description: Gather infos about Hetzner Storage Box Types. + +description: + - Gather infos about available Hetzner Storage Box Types. + - See the L(Storage Box Types documentation,https://docs.hetzner.cloud/reference/hetzner#storage-box-types) for more details. + - B(Experimental:) Storage Box support is experimental, breaking changes may occur within minor releases. + See https://github.com/ansible-collections/hetzner.hcloud/issues/756 for more details. + +author: + - Jonas Lammler (@jooola) + +options: + id: + description: + - ID of the Storage Box Type to get. + - If the ID is invalid, the module will fail. + type: int + name: + description: + - Name of the Storage Box Type to get. + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather Storage Box Types infos + hetzner.hcloud.storage_box_type_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_storage_box_type_info +""" + +RETURN = """ +hcloud_storage_box_type_info: + description: List of Storage Box Types. + returned: always + type: list + contains: + id: + description: ID of the Storage Box Type. + returned: success + type: int + sample: 1937415 + name: + description: Name of the Storage Box Type. + returned: success + type: str + sample: bx21 + description: + description: Description of the Storage Box Type. + returned: success + type: str + sample: BX21 + snapshot_limit: + description: Maximum number of allowed manual snapshots. + returned: success + type: int + sample: 10 + automatic_snapshot_limit: + description: Maximum number of snapshots created automatically by a snapshot plan. + returned: success + type: int + sample: 10 + subaccounts_limit: + description: Maximum number of subaccounts. + returned: success + type: int + sample: 200 + size: + description: Available storage in bytes. + returned: success + type: int + sample: 1099511627776 + deprecation: + description: Deprecation details about the Storage Box Type. + returned: when deprecated + type: dict + contains: + announced: + description: Date when the deprecation was announced. + returned: success + type: str + sample: "2025-06-02T00:00:00Z" + unavailable_after: + description: | + Date when the resource will stop being available. + + The resource will be removed from the list endpoint, but details + about the resource can be fetched using its ID. + returned: success + type: str + sample: "2025-09-02T00:00:00Z" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.experimental import storage_box_experimental_warning +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.storage_box_types import BoundStorageBoxType + + +class AnsibleStorageBoxTypeInfo(AnsibleHCloud): + represent = "storage_box_types" + + storage_box_types: list[BoundStorageBoxType] | None = None + + def __init__(self, module: AnsibleModule): + storage_box_experimental_warning(module) + super().__init__(module) + + def _prepare_result(self): + result = [] + + for o in self.storage_box_types or []: + if o is None: + continue + + result.append( + { + "id": o.id, + "name": o.name, + "description": o.description, + "snapshot_limit": o.snapshot_limit, + "automatic_snapshot_limit": o.automatic_snapshot_limit, + "subaccounts_limit": o.subaccounts_limit, + "size": o.size, + "deprecation": ( + { + "announced": o.deprecation.announced.isoformat(), + "unavailable_after": o.deprecation.unavailable_after.isoformat(), + } + if o.deprecation is not None + else None + ), + } + ) + return result + + def fetch(self): + try: + if (id_ := self.module.params.get("id")) is not None: + self.storage_box_types = [self.client.storage_box_types.get_by_id(id_)] + elif (name := self.module.params.get("name")) is not None: + self.storage_box_types = [self.client.storage_box_types.get_by_name(name)] + else: + self.storage_box_types = self.client.storage_box_types.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleStorageBoxTypeInfo.define_module() + o = AnsibleStorageBoxTypeInfo(module) + + o.fetch() + result = o.get_result() + + # Legacy return value naming pattern + result["hcloud_storage_box_type_info"] = result.pop(o.represent) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/common/defaults/main/common.yml b/tests/integration/common/defaults/main/common.yml index e8e4a1b..7bf8980 100644 --- a/tests/integration/common/defaults/main/common.yml +++ b/tests/integration/common/defaults/main/common.yml @@ -24,3 +24,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/certificate/defaults/main/common.yml b/tests/integration/targets/certificate/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/certificate/defaults/main/common.yml +++ b/tests/integration/targets/certificate/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/certificate_info/defaults/main/common.yml b/tests/integration/targets/certificate_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/certificate_info/defaults/main/common.yml +++ b/tests/integration/targets/certificate_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/datacenter_info/defaults/main/common.yml b/tests/integration/targets/datacenter_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/datacenter_info/defaults/main/common.yml +++ b/tests/integration/targets/datacenter_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/filter_all/defaults/main/common.yml b/tests/integration/targets/filter_all/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/filter_all/defaults/main/common.yml +++ b/tests/integration/targets/filter_all/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/firewall/defaults/main/common.yml b/tests/integration/targets/firewall/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/firewall/defaults/main/common.yml +++ b/tests/integration/targets/firewall/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/firewall_info/defaults/main/common.yml b/tests/integration/targets/firewall_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/firewall_info/defaults/main/common.yml +++ b/tests/integration/targets/firewall_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/firewall_resource/defaults/main/common.yml b/tests/integration/targets/firewall_resource/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/firewall_resource/defaults/main/common.yml +++ b/tests/integration/targets/firewall_resource/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/floating_ip/defaults/main/common.yml b/tests/integration/targets/floating_ip/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/floating_ip/defaults/main/common.yml +++ b/tests/integration/targets/floating_ip/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/floating_ip_info/defaults/main/common.yml b/tests/integration/targets/floating_ip_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/floating_ip_info/defaults/main/common.yml +++ b/tests/integration/targets/floating_ip_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/image_info/defaults/main/common.yml b/tests/integration/targets/image_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/image_info/defaults/main/common.yml +++ b/tests/integration/targets/image_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/iso_info/defaults/main/common.yml b/tests/integration/targets/iso_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/iso_info/defaults/main/common.yml +++ b/tests/integration/targets/iso_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/load_balancer/defaults/main/common.yml b/tests/integration/targets/load_balancer/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/load_balancer/defaults/main/common.yml +++ b/tests/integration/targets/load_balancer/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/load_balancer_info/defaults/main/common.yml b/tests/integration/targets/load_balancer_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/load_balancer_info/defaults/main/common.yml +++ b/tests/integration/targets/load_balancer_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/load_balancer_network/defaults/main/common.yml b/tests/integration/targets/load_balancer_network/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/load_balancer_network/defaults/main/common.yml +++ b/tests/integration/targets/load_balancer_network/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/load_balancer_service/defaults/main/common.yml b/tests/integration/targets/load_balancer_service/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/load_balancer_service/defaults/main/common.yml +++ b/tests/integration/targets/load_balancer_service/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/load_balancer_target/defaults/main/common.yml b/tests/integration/targets/load_balancer_target/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/load_balancer_target/defaults/main/common.yml +++ b/tests/integration/targets/load_balancer_target/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/load_balancer_type_info/defaults/main/common.yml b/tests/integration/targets/load_balancer_type_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/load_balancer_type_info/defaults/main/common.yml +++ b/tests/integration/targets/load_balancer_type_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/location_info/defaults/main/common.yml b/tests/integration/targets/location_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/location_info/defaults/main/common.yml +++ b/tests/integration/targets/location_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/network/defaults/main/common.yml b/tests/integration/targets/network/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/network/defaults/main/common.yml +++ b/tests/integration/targets/network/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/network_info/defaults/main/common.yml b/tests/integration/targets/network_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/network_info/defaults/main/common.yml +++ b/tests/integration/targets/network_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/placement_group/defaults/main/common.yml b/tests/integration/targets/placement_group/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/placement_group/defaults/main/common.yml +++ b/tests/integration/targets/placement_group/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/primary_ip/defaults/main/common.yml b/tests/integration/targets/primary_ip/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/primary_ip/defaults/main/common.yml +++ b/tests/integration/targets/primary_ip/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/primary_ip_info/defaults/main/common.yml b/tests/integration/targets/primary_ip_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/primary_ip_info/defaults/main/common.yml +++ b/tests/integration/targets/primary_ip_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/rdns/defaults/main/common.yml b/tests/integration/targets/rdns/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/rdns/defaults/main/common.yml +++ b/tests/integration/targets/rdns/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/route/defaults/main/common.yml b/tests/integration/targets/route/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/route/defaults/main/common.yml +++ b/tests/integration/targets/route/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/server/defaults/main/common.yml b/tests/integration/targets/server/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/server/defaults/main/common.yml +++ b/tests/integration/targets/server/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/server_info/defaults/main/common.yml b/tests/integration/targets/server_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/server_info/defaults/main/common.yml +++ b/tests/integration/targets/server_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/server_network/defaults/main/common.yml b/tests/integration/targets/server_network/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/server_network/defaults/main/common.yml +++ b/tests/integration/targets/server_network/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/server_type_info/defaults/main/common.yml b/tests/integration/targets/server_type_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/server_type_info/defaults/main/common.yml +++ b/tests/integration/targets/server_type_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/ssh_key/defaults/main/common.yml b/tests/integration/targets/ssh_key/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/ssh_key/defaults/main/common.yml +++ b/tests/integration/targets/ssh_key/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/ssh_key_info/defaults/main/common.yml b/tests/integration/targets/ssh_key_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/ssh_key_info/defaults/main/common.yml +++ b/tests/integration/targets/ssh_key_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box/aliases b/tests/integration/targets/storage_box/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box/defaults/main/common.yml b/tests/integration/targets/storage_box/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box/defaults/main/main.yml b/tests/integration/targets/storage_box/defaults/main/main.yml new file mode 100644 index 0000000..69c5659 --- /dev/null +++ b/tests/integration/targets/storage_box/defaults/main/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_storage_box_name: "{{ hcloud_ns }}" +hcloud_ssh_key_name: "{{ hcloud_ns }}" +hcloud_storage_box_password: 1-secret-PASSW0RD-=) diff --git a/tests/integration/targets/storage_box/meta/main.yml b/tests/integration/targets/storage_box/meta/main.yml new file mode 100644 index 0000000..3a96ecb --- /dev/null +++ b/tests/integration/targets/storage_box/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_ssh_keypair diff --git a/tests/integration/targets/storage_box/tasks/cleanup.yml b/tests/integration/targets/storage_box/tasks/cleanup.yml new file mode 100644 index 0000000..d0ab906 --- /dev/null +++ b/tests/integration/targets/storage_box/tasks/cleanup.yml @@ -0,0 +1,5 @@ +--- +- name: Cleanup test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent diff --git a/tests/integration/targets/storage_box/tasks/main.yml b/tests/integration/targets/storage_box/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box/tasks/prepare.yml b/tests/integration/targets/storage_box/tasks/prepare.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/tests/integration/targets/storage_box/tasks/prepare.yml @@ -0,0 +1 @@ +--- diff --git a/tests/integration/targets/storage_box/tasks/test.yml b/tests/integration/targets/storage_box/tasks/test.yml new file mode 100644 index 0000000..d16b6a0 --- /dev/null +++ b/tests/integration/targets/storage_box/tasks/test.yml @@ -0,0 +1,247 @@ +--- +- name: Test missing required parameters + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: present + ignore_errors: true + register: result +- name: Verify missing required parameters + ansible.builtin.assert: + that: + - result is failed + - 'result.msg == "missing required arguments: storage_box_type, location, password"' + +- name: Test create with check mode + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + labels: + key: value + check_mode: true + register: result +- name: Verify create with check mode + ansible.builtin.assert: + that: + - result is changed + +- name: Test create + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + zfs_enabled: false + snapshot_plan: + max_snapshots: 10 + hour: 3 + minute: 30 + labels: + key: value + register: result +- name: Verify create + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box.id is not none + - result.hcloud_storage_box.name == hcloud_storage_box_name + - result.hcloud_storage_box.storage_box_type == hcloud_storage_box_type_name + - result.hcloud_storage_box.location == hcloud_location_name + - result.hcloud_storage_box.labels.key == "value" + - result.hcloud_storage_box.delete_protection == false + - result.hcloud_storage_box.access_settings.reachable_externally == false + - result.hcloud_storage_box.access_settings.samba_enabled == false + - result.hcloud_storage_box.access_settings.ssh_enabled == true + - result.hcloud_storage_box.access_settings.webdav_enabled == false + - result.hcloud_storage_box.access_settings.zfs_enabled == false + - result.hcloud_storage_box.snapshot_plan.max_snapshots == 10 + - result.hcloud_storage_box.snapshot_plan.hour == 3 + - result.hcloud_storage_box.snapshot_plan.minute == 30 + - result.hcloud_storage_box.status == "active" + - result.hcloud_storage_box.server is not none + - result.hcloud_storage_box.system is not none + - result.hcloud_storage_box.username is not none + - result.hcloud_storage_box.stats.size >= 0 + - result.hcloud_storage_box.stats.size_data >= 0 + - result.hcloud_storage_box.stats.size_snapshots >= 0 + +- name: Test create idempotency + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + zfs_enabled: false + snapshot_plan: + max_snapshots: 10 + hour: 3 + minute: 30 + labels: + key: value + register: result +- name: Verify create idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test update name + hetzner.hcloud.storage_box: + id: "{{ result.hcloud_storage_box.id }}" + name: "{{ hcloud_storage_box_name }}-changed" # Update + register: result +- name: Verify update name + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box.name == hcloud_storage_box_name + "-changed" + + # Ensure snapshot plan was not changed + - result.hcloud_storage_box.snapshot_plan.max_snapshots == 10 + - result.hcloud_storage_box.snapshot_plan.hour == 3 + - result.hcloud_storage_box.snapshot_plan.minute == 30 + +- name: Test update + hetzner.hcloud.storage_box: + id: "{{ result.hcloud_storage_box.id }}" + name: "{{ hcloud_storage_box_name }}" # Update + type: "{{ hcloud_storage_box_type_upgrade_name }}" # Update + location: "{{ hcloud_location_name }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: # Update + ssh_enabled: false + reachable_externally: true + snapshot_plan: + max_snapshots: 10 + hour: 4 # Update + minute: 30 + labels: + key: changed # Update + delete_protection: true # Update + register: result +- name: Verify update + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box.id is not none + - result.hcloud_storage_box.name == hcloud_storage_box_name + - result.hcloud_storage_box.storage_box_type == hcloud_storage_box_type_upgrade_name + - result.hcloud_storage_box.location == hcloud_location_name + - result.hcloud_storage_box.labels.key == "changed" + - result.hcloud_storage_box.delete_protection == true + - result.hcloud_storage_box.access_settings.reachable_externally == true + - result.hcloud_storage_box.access_settings.samba_enabled == false + - result.hcloud_storage_box.access_settings.ssh_enabled == false + - result.hcloud_storage_box.access_settings.webdav_enabled == false + - result.hcloud_storage_box.access_settings.zfs_enabled == false + - result.hcloud_storage_box.snapshot_plan.max_snapshots == 10 + - result.hcloud_storage_box.snapshot_plan.hour == 4 + - result.hcloud_storage_box.snapshot_plan.minute == 30 + +- name: Test update idempotency + hetzner.hcloud.storage_box: + id: "{{ result.hcloud_storage_box.id }}" + name: "{{ hcloud_storage_box_name }}" # Update + type: "{{ hcloud_storage_box_type_upgrade_name }}" # Update + location: "{{ hcloud_location_name }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: # Update + ssh_enabled: false + reachable_externally: true + snapshot_plan: + max_snapshots: 10 + hour: 4 # Update + minute: 30 + labels: + key: changed # Update + delete_protection: true # Update + register: result +- name: Verify update idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test delete with delete protection + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent + ignore_errors: true + register: result +- name: Verify delete with delete protection + ansible.builtin.assert: + that: + - result is failed + - result.failure.code == "protected" + +- name: Test update delete protection + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + delete_protection: false + register: result +- name: Verify update delete protection + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box.delete_protection == false + +- name: Test update snapshot plan + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + snapshot_plan: null + register: result +- name: Verify update snapshot plan + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box.snapshot_plan is none + +- name: Test reset password + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + password: "{{ hcloud_storage_box_password }}" + state: reset_password + register: result +- name: Verify reset password + ansible.builtin.assert: + that: + - result is changed + +- name: Create snapshot + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ hcloud_storage_box_name }}" + description: test-rollback + register: _rollback_snapshot + +- name: Test rollback snapshot + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + snapshot: "{{ _rollback_snapshot.hcloud_storage_box_snapshot.id }}" + state: rollback_snapshot + register: result +- name: Verify rollback snapshot + ansible.builtin.assert: + that: + - result is changed + +- name: Test delete + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent + register: result +- name: Verify delete + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box is none diff --git a/tests/integration/targets/storage_box_info/aliases b/tests/integration/targets/storage_box_info/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box_info/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box_info/defaults/main/common.yml b/tests/integration/targets/storage_box_info/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box_info/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box_info/defaults/main/main.yml b/tests/integration/targets/storage_box_info/defaults/main/main.yml new file mode 100644 index 0000000..69c5659 --- /dev/null +++ b/tests/integration/targets/storage_box_info/defaults/main/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_storage_box_name: "{{ hcloud_ns }}" +hcloud_ssh_key_name: "{{ hcloud_ns }}" +hcloud_storage_box_password: 1-secret-PASSW0RD-=) diff --git a/tests/integration/targets/storage_box_info/meta/main.yml b/tests/integration/targets/storage_box_info/meta/main.yml new file mode 100644 index 0000000..3a96ecb --- /dev/null +++ b/tests/integration/targets/storage_box_info/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_ssh_keypair diff --git a/tests/integration/targets/storage_box_info/tasks/cleanup.yml b/tests/integration/targets/storage_box_info/tasks/cleanup.yml new file mode 100644 index 0000000..d0ab906 --- /dev/null +++ b/tests/integration/targets/storage_box_info/tasks/cleanup.yml @@ -0,0 +1,5 @@ +--- +- name: Cleanup test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent diff --git a/tests/integration/targets/storage_box_info/tasks/main.yml b/tests/integration/targets/storage_box_info/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box_info/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box_info/tasks/prepare.yml b/tests/integration/targets/storage_box_info/tasks/prepare.yml new file mode 100644 index 0000000..ab381a9 --- /dev/null +++ b/tests/integration/targets/storage_box_info/tasks/prepare.yml @@ -0,0 +1,15 @@ +--- +- name: Create test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + zfs_enabled: false + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box diff --git a/tests/integration/targets/storage_box_info/tasks/test.yml b/tests/integration/targets/storage_box_info/tasks/test.yml new file mode 100644 index 0000000..e5971ef --- /dev/null +++ b/tests/integration/targets/storage_box_info/tasks/test.yml @@ -0,0 +1,83 @@ +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Gather hcloud_storage_box_info + hetzner.hcloud.storage_box_info: + register: result +- name: Verify hcloud_storage_box_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_info | list | count >= 1 + +- name: Gather hcloud_storage_box_info in check mode + hetzner.hcloud.storage_box_info: + check_mode: true + register: result +- name: Verify hcloud_storage_box_info in check mode + ansible.builtin.assert: + that: + - result.hcloud_storage_box_info | list | count >= 1 + +- name: Gather hcloud_storage_box_info with label + hetzner.hcloud.storage_box_info: + label_selector: key={{ hcloud_ns }} + register: result +- name: Verify hcloud_storage_box_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_info | list | count == 1 + +- name: Gather hcloud_storage_box_info with correct id + hetzner.hcloud.storage_box_info: + id: "{{ test_storage_box.hcloud_storage_box.id }}" + register: result +- name: Verify hcloud_storage_box_info with correct id + ansible.builtin.assert: + that: + - result.hcloud_storage_box_info | list | count == 1 + - result.hcloud_storage_box_info[0].id == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_info[0].name == hcloud_storage_box_name + - result.hcloud_storage_box_info[0].storage_box_type == hcloud_storage_box_type_name + - result.hcloud_storage_box_info[0].location == hcloud_location_name + - result.hcloud_storage_box_info[0].labels.key == hcloud_ns + - result.hcloud_storage_box_info[0].delete_protection == false + - result.hcloud_storage_box_info[0].access_settings.reachable_externally == false + - result.hcloud_storage_box_info[0].access_settings.samba_enabled == false + - result.hcloud_storage_box_info[0].access_settings.ssh_enabled == true + - result.hcloud_storage_box_info[0].access_settings.webdav_enabled == false + - result.hcloud_storage_box_info[0].access_settings.zfs_enabled == false + - result.hcloud_storage_box_info[0].status == "active" + - result.hcloud_storage_box_info[0].server is not none + - result.hcloud_storage_box_info[0].system is not none + - result.hcloud_storage_box_info[0].username is not none + - result.hcloud_storage_box_info[0].stats.size >= 0 + - result.hcloud_storage_box_info[0].stats.size_data >= 0 + - result.hcloud_storage_box_info[0].stats.size_snapshots >= 0 + +- name: Gather hcloud_storage_box_info with wrong id + hetzner.hcloud.storage_box_info: + id: "{{ test_storage_box.hcloud_storage_box.id }}4321" + ignore_errors: true + register: result +- name: Verify hcloud_storage_box_info with wrong id + ansible.builtin.assert: + that: + - result is failed + +- name: Gather hcloud_storage_box_info with correct name + hetzner.hcloud.storage_box_info: + name: "{{ hcloud_storage_box_name }}" + register: result +- name: Verify hcloud_storage_box_info with correct name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_info | list | count == 1 + +- name: Gather hcloud_storage_box_info with wrong name + hetzner.hcloud.storage_box_info: + name: "{{ hcloud_storage_box_name }}-invalid" + register: result +- name: Verify hcloud_storage_box_info with wrong name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_info | list | count == 0 diff --git a/tests/integration/targets/storage_box_snapshot/aliases b/tests/integration/targets/storage_box_snapshot/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box_snapshot/defaults/main/common.yml b/tests/integration/targets/storage_box_snapshot/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box_snapshot/defaults/main/main.yml b/tests/integration/targets/storage_box_snapshot/defaults/main/main.yml new file mode 100644 index 0000000..69c5659 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/defaults/main/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_storage_box_name: "{{ hcloud_ns }}" +hcloud_ssh_key_name: "{{ hcloud_ns }}" +hcloud_storage_box_password: 1-secret-PASSW0RD-=) diff --git a/tests/integration/targets/storage_box_snapshot/meta/main.yml b/tests/integration/targets/storage_box_snapshot/meta/main.yml new file mode 100644 index 0000000..3a96ecb --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_ssh_keypair diff --git a/tests/integration/targets/storage_box_snapshot/tasks/cleanup.yml b/tests/integration/targets/storage_box_snapshot/tasks/cleanup.yml new file mode 100644 index 0000000..d0ab906 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/tasks/cleanup.yml @@ -0,0 +1,5 @@ +--- +- name: Cleanup test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent diff --git a/tests/integration/targets/storage_box_snapshot/tasks/main.yml b/tests/integration/targets/storage_box_snapshot/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box_snapshot/tasks/prepare.yml b/tests/integration/targets/storage_box_snapshot/tasks/prepare.yml new file mode 100644 index 0000000..5e009ac --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/tasks/prepare.yml @@ -0,0 +1,15 @@ +--- +- name: Create test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + reachable_externally: false + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box diff --git a/tests/integration/targets/storage_box_snapshot/tasks/test.yml b/tests/integration/targets/storage_box_snapshot/tasks/test.yml new file mode 100644 index 0000000..1bdac38 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot/tasks/test.yml @@ -0,0 +1,119 @@ +--- +- name: Test missing required parameters # noqa: args[module] + hetzner.hcloud.storage_box_snapshot: + ignore_errors: true + register: result +- name: Verify missing required parameters + ansible.builtin.assert: + that: + - result is failed + - 'result.msg == "missing required arguments: storage_box"' + +- name: Test create with check mode + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ hcloud_storage_box_name }}" + description: migration-1 + labels: + key: value + check_mode: true + register: result +- name: Verify create with check mode + ansible.builtin.assert: + that: + - result is changed + +- name: Test create + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ hcloud_storage_box_name }}" + description: migration-1 + labels: + key: value + register: result +- name: Verify create + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_snapshot.storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_snapshot.id is not none + - result.hcloud_storage_box_snapshot.name is not none + - result.hcloud_storage_box_snapshot.description == "migration-1" + - result.hcloud_storage_box_snapshot.labels.key == "value" + - result.hcloud_storage_box_snapshot.is_automatic is false + - result.hcloud_storage_box_snapshot.stats.size >= 0 + - result.hcloud_storage_box_snapshot.stats.size_filesystem >= 0 + - result.hcloud_storage_box_snapshot.created is not none + +- name: Save Storage Box Snapshot + ansible.builtin.set_fact: + _storage_box_snapshot: "{{ result.hcloud_storage_box_snapshot }}" + +- name: Test create idempotency + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ _storage_box_snapshot.id }}" # Create is not idempotent, only with id or name + description: migration-1 + labels: + key: value + register: result +- name: Verify create idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test update + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ _storage_box_snapshot.id }}" + description: migration-changed # Update + labels: + key: changed # Update + register: result +- name: Verify update + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_snapshot.storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_snapshot.id is not none + - result.hcloud_storage_box_snapshot.name is not none + - result.hcloud_storage_box_snapshot.description == "migration-changed" + - result.hcloud_storage_box_snapshot.labels.key == "changed" + - result.hcloud_storage_box_snapshot.is_automatic is false + - result.hcloud_storage_box_snapshot.stats.size >= 0 + - result.hcloud_storage_box_snapshot.stats.size_filesystem >= 0 + - result.hcloud_storage_box_snapshot.created is not none + +- name: Test update idempotency + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.name }}" + name: "{{ _storage_box_snapshot.name }}" + description: migration-changed # Update + labels: + key: changed # Update + register: result +- name: Verify update idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test delete + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.name }}" + name: "{{ _storage_box_snapshot.name }}" + state: absent + register: result +- name: Verify delete + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_snapshot is none + +- name: Test delete idempotency + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.name }}" + name: "{{ _storage_box_snapshot.name }}" + state: absent + register: result +- name: Verify delete idempotency + ansible.builtin.assert: + that: + - result is not changed diff --git a/tests/integration/targets/storage_box_snapshot_info/aliases b/tests/integration/targets/storage_box_snapshot_info/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box_snapshot_info/defaults/main/common.yml b/tests/integration/targets/storage_box_snapshot_info/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box_snapshot_info/defaults/main/main.yml b/tests/integration/targets/storage_box_snapshot_info/defaults/main/main.yml new file mode 100644 index 0000000..69c5659 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/defaults/main/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_storage_box_name: "{{ hcloud_ns }}" +hcloud_ssh_key_name: "{{ hcloud_ns }}" +hcloud_storage_box_password: 1-secret-PASSW0RD-=) diff --git a/tests/integration/targets/storage_box_snapshot_info/meta/main.yml b/tests/integration/targets/storage_box_snapshot_info/meta/main.yml new file mode 100644 index 0000000..3a96ecb --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_ssh_keypair diff --git a/tests/integration/targets/storage_box_snapshot_info/tasks/cleanup.yml b/tests/integration/targets/storage_box_snapshot_info/tasks/cleanup.yml new file mode 100644 index 0000000..d0ab906 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/tasks/cleanup.yml @@ -0,0 +1,5 @@ +--- +- name: Cleanup test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent diff --git a/tests/integration/targets/storage_box_snapshot_info/tasks/main.yml b/tests/integration/targets/storage_box_snapshot_info/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box_snapshot_info/tasks/prepare.yml b/tests/integration/targets/storage_box_snapshot_info/tasks/prepare.yml new file mode 100644 index 0000000..e48e260 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/tasks/prepare.yml @@ -0,0 +1,28 @@ +--- +- name: Create test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + reachable_externally: false + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box + +- name: Create test_storage_box_snapshot1 + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + description: snapshot1 + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box_snapshot1 + +- name: Create test_storage_box_snapshot2 + hetzner.hcloud.storage_box_snapshot: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + register: test_storage_box_snapshot2 diff --git a/tests/integration/targets/storage_box_snapshot_info/tasks/test.yml b/tests/integration/targets/storage_box_snapshot_info/tasks/test.yml new file mode 100644 index 0000000..aa73890 --- /dev/null +++ b/tests/integration/targets/storage_box_snapshot_info/tasks/test.yml @@ -0,0 +1,81 @@ +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Gather hcloud_storage_box_snapshot_info + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + register: result +- name: Verify hcloud_storage_box_snapshot_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_snapshot_info | list | count == 2 + +- name: Gather hcloud_storage_box_snapshot_info in check mode + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + check_mode: true + register: result +- name: Verify hcloud_storage_box_snapshot_info in check mode + ansible.builtin.assert: + that: + - result.hcloud_storage_box_snapshot_info | list | count == 2 + +- name: Gather hcloud_storage_box_snapshot_info with label + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + label_selector: "key={{ hcloud_ns }}" + register: result +- name: Verify hcloud_storage_box_snapshot_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_snapshot_info | list | count == 1 + +- name: Gather hcloud_storage_box_snapshot_info with correct id + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ test_storage_box_snapshot1.hcloud_storage_box_snapshot.id }}" + register: result +- name: Verify hcloud_storage_box_snapshot_info with correct id + ansible.builtin.assert: + that: + - result.hcloud_storage_box_snapshot_info | list | count == 1 + - result.hcloud_storage_box_snapshot_info[0].storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_snapshot_info[0].id is not none + - result.hcloud_storage_box_snapshot_info[0].name is not none + - result.hcloud_storage_box_snapshot_info[0].description == "snapshot1" + - result.hcloud_storage_box_snapshot_info[0].labels.key == hcloud_ns + - result.hcloud_storage_box_snapshot_info[0].is_automatic is false + - result.hcloud_storage_box_snapshot_info[0].stats.size >= 0 + - result.hcloud_storage_box_snapshot_info[0].stats.size_filesystem >= 0 + - result.hcloud_storage_box_snapshot_info[0].created is not none + +- name: Gather hcloud_storage_box_snapshot_info with wrong id + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ test_storage_box_snapshot1.hcloud_storage_box_snapshot.id }}4321" + ignore_errors: true + register: result +- name: Verify hcloud_storage_box_snapshot_info with wrong id + ansible.builtin.assert: + that: + - result is failed + +- name: Gather hcloud_storage_box_snapshot_info with correct name + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: "{{ test_storage_box_snapshot1.hcloud_storage_box_snapshot.name }}" + register: result +- name: Verify hcloud_storage_box_snapshot_info with correct name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_snapshot_info | list | count == 1 + +- name: Gather hcloud_storage_box_snapshot_info with wrong name + hetzner.hcloud.storage_box_snapshot_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: "{{ test_storage_box_snapshot1.hcloud_storage_box_snapshot.name }}-invalid" + register: result +- name: Verify hcloud_storage_box_snapshot_info with wrong name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_snapshot_info | list | count == 0 diff --git a/tests/integration/targets/storage_box_subaccount/aliases b/tests/integration/targets/storage_box_subaccount/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box_subaccount/defaults/main/common.yml b/tests/integration/targets/storage_box_subaccount/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box_subaccount/defaults/main/main.yml b/tests/integration/targets/storage_box_subaccount/defaults/main/main.yml new file mode 100644 index 0000000..69c5659 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/defaults/main/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_storage_box_name: "{{ hcloud_ns }}" +hcloud_ssh_key_name: "{{ hcloud_ns }}" +hcloud_storage_box_password: 1-secret-PASSW0RD-=) diff --git a/tests/integration/targets/storage_box_subaccount/meta/main.yml b/tests/integration/targets/storage_box_subaccount/meta/main.yml new file mode 100644 index 0000000..3a96ecb --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_ssh_keypair diff --git a/tests/integration/targets/storage_box_subaccount/tasks/cleanup.yml b/tests/integration/targets/storage_box_subaccount/tasks/cleanup.yml new file mode 100644 index 0000000..d0ab906 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/tasks/cleanup.yml @@ -0,0 +1,5 @@ +--- +- name: Cleanup test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent diff --git a/tests/integration/targets/storage_box_subaccount/tasks/main.yml b/tests/integration/targets/storage_box_subaccount/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box_subaccount/tasks/prepare.yml b/tests/integration/targets/storage_box_subaccount/tasks/prepare.yml new file mode 100644 index 0000000..5e009ac --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/tasks/prepare.yml @@ -0,0 +1,15 @@ +--- +- name: Create test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + reachable_externally: false + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box diff --git a/tests/integration/targets/storage_box_subaccount/tasks/test.yml b/tests/integration/targets/storage_box_subaccount/tasks/test.yml new file mode 100644 index 0000000..194dddc --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount/tasks/test.yml @@ -0,0 +1,190 @@ +--- +- name: Test missing required parameters # noqa: args[module] + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ hcloud_storage_box_name }}" + ignore_errors: true + register: result +- name: Verify missing required parameters + ansible.builtin.assert: + that: + - result is failed + - 'result.msg == "one of the following is required: id, name"' + +- name: Test create with check mode + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ hcloud_storage_box_name }}" + name: subaccount1 + description: backups from subaccount1 + home_directory: backups/subaccount1 + password: "{{ hcloud_storage_box_password }}" + labels: + key: value + access_settings: + ssh_enabled: true + readonly: false + check_mode: true + register: result +- name: Verify create with check mode + ansible.builtin.assert: + that: + - result is changed + +- name: Test create + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ hcloud_storage_box_name }}" + name: subaccount1 + description: backups from subaccount1 + home_directory: backups/subaccount1 + password: "{{ hcloud_storage_box_password }}" + labels: + key: value + access_settings: + ssh_enabled: true + readonly: false + register: result +- name: Verify create + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_subaccount.storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_subaccount.id is not none + - result.hcloud_storage_box_subaccount.name == "subaccount1" + - result.hcloud_storage_box_subaccount.description == "backups from subaccount1" + - result.hcloud_storage_box_subaccount.home_directory == "backups/subaccount1" + - result.hcloud_storage_box_subaccount.username is not none + - result.hcloud_storage_box_subaccount.server is not none + - result.hcloud_storage_box_subaccount.labels.key == "value" + - result.hcloud_storage_box_subaccount.access_settings.reachable_externally == false + - result.hcloud_storage_box_subaccount.access_settings.samba_enabled == false + - result.hcloud_storage_box_subaccount.access_settings.ssh_enabled == true + - result.hcloud_storage_box_subaccount.access_settings.webdav_enabled == false + - result.hcloud_storage_box_subaccount.access_settings.readonly == false + - result.hcloud_storage_box_subaccount.created is not none + +- name: Test create idempotency + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount1 + description: backups from subaccount1 + home_directory: backups/subaccount1 + password: "{{ hcloud_storage_box_password }}" + labels: + key: value + access_settings: + ssh_enabled: true + readonly: false + register: result +- name: Verify create idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test update + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount1 + description: backups from subaccount2 # Update + home_directory: backups/subaccount2 # Update + password: "{{ hcloud_storage_box_password }}" + labels: + key: changed # Update + access_settings: + ssh_enabled: true + readonly: true # Update + register: result +- name: Verify update + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_subaccount.storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_subaccount.id is not none + - result.hcloud_storage_box_subaccount.name == "subaccount1" + - result.hcloud_storage_box_subaccount.description == "backups from subaccount2" + - result.hcloud_storage_box_subaccount.home_directory == "backups/subaccount2" + - result.hcloud_storage_box_subaccount.username is not none + - result.hcloud_storage_box_subaccount.server is not none + - result.hcloud_storage_box_subaccount.labels.key == "changed" + - result.hcloud_storage_box_subaccount.access_settings.reachable_externally == false + - result.hcloud_storage_box_subaccount.access_settings.samba_enabled == false + - result.hcloud_storage_box_subaccount.access_settings.ssh_enabled == true + - result.hcloud_storage_box_subaccount.access_settings.webdav_enabled == false + - result.hcloud_storage_box_subaccount.access_settings.readonly == true + - result.hcloud_storage_box_subaccount.created is not none + +- name: Test update idempotency + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount1 + description: backups from subaccount2 # Update + home_directory: backups/subaccount2 # Update + password: "{{ hcloud_storage_box_password }}" + labels: + key: changed # Update + access_settings: + ssh_enabled: true + readonly: true # Update + register: result +- name: Verify update idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test update name + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ result.hcloud_storage_box_subaccount.id }}" + name: subaccount2 + register: result +- name: Verify update + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_subaccount.storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_subaccount.id is not none + - result.hcloud_storage_box_subaccount.name == "subaccount2" # Update + - result.hcloud_storage_box_subaccount.description == "backups from subaccount2" + - result.hcloud_storage_box_subaccount.home_directory == "backups/subaccount2" + - result.hcloud_storage_box_subaccount.username is not none + - result.hcloud_storage_box_subaccount.server is not none + - result.hcloud_storage_box_subaccount.labels.key == "changed" + - result.hcloud_storage_box_subaccount.access_settings.reachable_externally == false + - result.hcloud_storage_box_subaccount.access_settings.samba_enabled == false + - result.hcloud_storage_box_subaccount.access_settings.ssh_enabled == true + - result.hcloud_storage_box_subaccount.access_settings.webdav_enabled == false + - result.hcloud_storage_box_subaccount.access_settings.readonly == true + - result.hcloud_storage_box_subaccount.created is not none + +- name: Test reset password + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount2 + password: "{{ hcloud_storage_box_password }}" + state: reset_password + register: result +- name: Verify reset password + ansible.builtin.assert: + that: + - result is changed + +- name: Test delete + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount2 + state: absent + register: result +- name: Verify delete + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_storage_box_subaccount is none + +- name: Test delete idempotency + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount2 + state: absent + register: result +- name: Verify delete idempotency + ansible.builtin.assert: + that: + - result is not changed diff --git a/tests/integration/targets/storage_box_subaccount_info/aliases b/tests/integration/targets/storage_box_subaccount_info/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box_subaccount_info/defaults/main/common.yml b/tests/integration/targets/storage_box_subaccount_info/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box_subaccount_info/defaults/main/main.yml b/tests/integration/targets/storage_box_subaccount_info/defaults/main/main.yml new file mode 100644 index 0000000..69c5659 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/defaults/main/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_storage_box_name: "{{ hcloud_ns }}" +hcloud_ssh_key_name: "{{ hcloud_ns }}" +hcloud_storage_box_password: 1-secret-PASSW0RD-=) diff --git a/tests/integration/targets/storage_box_subaccount_info/meta/main.yml b/tests/integration/targets/storage_box_subaccount_info/meta/main.yml new file mode 100644 index 0000000..3a96ecb --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_ssh_keypair diff --git a/tests/integration/targets/storage_box_subaccount_info/tasks/cleanup.yml b/tests/integration/targets/storage_box_subaccount_info/tasks/cleanup.yml new file mode 100644 index 0000000..d0ab906 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/tasks/cleanup.yml @@ -0,0 +1,5 @@ +--- +- name: Cleanup test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + state: absent diff --git a/tests/integration/targets/storage_box_subaccount_info/tasks/main.yml b/tests/integration/targets/storage_box_subaccount_info/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box_subaccount_info/tasks/prepare.yml b/tests/integration/targets/storage_box_subaccount_info/tasks/prepare.yml new file mode 100644 index 0000000..5074e01 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/tasks/prepare.yml @@ -0,0 +1,37 @@ +--- +- name: Create test_storage_box + hetzner.hcloud.storage_box: + name: "{{ hcloud_storage_box_name }}" + type: "{{ hcloud_storage_box_type_name }}" + location: "{{ hcloud_location_name }}" + password: "{{ hcloud_storage_box_password }}" + ssh_keys: + - "{{ test_ssh_keypair.public_key }}" + access_settings: + ssh_enabled: true + reachable_externally: false + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box + +- name: Create test_storage_box_subaccount1 + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount1 + description: backups from subaccount1 + home_directory: backups/subaccount1 + password: "{{ hcloud_storage_box_password }}" + access_settings: + ssh_enabled: true + readonly: false + labels: + key: "{{ hcloud_ns }}" + register: test_storage_box_subaccount1 + +- name: Create test_storage_box_subaccount2 + hetzner.hcloud.storage_box_subaccount: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: subaccount2 + home_directory: backups/subaccount2 + password: "{{ hcloud_storage_box_password }}" + register: test_storage_box_subaccount2 diff --git a/tests/integration/targets/storage_box_subaccount_info/tasks/test.yml b/tests/integration/targets/storage_box_subaccount_info/tasks/test.yml new file mode 100644 index 0000000..0178009 --- /dev/null +++ b/tests/integration/targets/storage_box_subaccount_info/tasks/test.yml @@ -0,0 +1,86 @@ +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Gather hcloud_storage_box_subaccount_info + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + register: result +- name: Verify hcloud_storage_box_subaccount_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_subaccount_info | list | count == 2 + +- name: Gather hcloud_storage_box_subaccount_info in check mode + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + check_mode: true + register: result +- name: Verify hcloud_storage_box_subaccount_info in check mode + ansible.builtin.assert: + that: + - result.hcloud_storage_box_subaccount_info | list | count == 2 + +- name: Gather hcloud_storage_box_subaccount_info with label + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + label_selector: "key={{ hcloud_ns }}" + register: result +- name: Verify hcloud_storage_box_subaccount_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_subaccount_info | list | count == 1 + +- name: Gather hcloud_storage_box_subaccount_info with correct id + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ test_storage_box_subaccount1.hcloud_storage_box_subaccount.id }}" + register: result +- name: Verify hcloud_storage_box_subaccount_info with correct id + ansible.builtin.assert: + that: + - result.hcloud_storage_box_subaccount_info | list | count == 1 + - result.hcloud_storage_box_subaccount_info[0].storage_box == test_storage_box.hcloud_storage_box.id + - result.hcloud_storage_box_subaccount_info[0].id is not none + - result.hcloud_storage_box_subaccount_info[0].name == "subaccount1" + - result.hcloud_storage_box_subaccount_info[0].home_directory == "backups/subaccount1" + - result.hcloud_storage_box_subaccount_info[0].username is not none + - result.hcloud_storage_box_subaccount_info[0].server is not none + - result.hcloud_storage_box_subaccount_info[0].description == "backups from subaccount1" + - result.hcloud_storage_box_subaccount_info[0].labels.key == hcloud_ns + - result.hcloud_storage_box_subaccount_info[0].access_settings.reachable_externally == false + - result.hcloud_storage_box_subaccount_info[0].access_settings.samba_enabled == false + - result.hcloud_storage_box_subaccount_info[0].access_settings.ssh_enabled == true + - result.hcloud_storage_box_subaccount_info[0].access_settings.webdav_enabled == false + - result.hcloud_storage_box_subaccount_info[0].access_settings.readonly == false + - result.hcloud_storage_box_subaccount_info[0].created is not none + +- name: Gather hcloud_storage_box_subaccount_info with wrong id + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + id: "{{ test_storage_box_subaccount1.hcloud_storage_box_subaccount.id }}4321" + ignore_errors: true + register: result +- name: Verify hcloud_storage_box_subaccount_info with wrong id + ansible.builtin.assert: + that: + - result is failed + +- name: Gather hcloud_storage_box_subaccount_info with correct name + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: "{{ test_storage_box_subaccount1.hcloud_storage_box_subaccount.name }}" + register: result +- name: Verify hcloud_storage_box_subaccount_info with correct name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_subaccount_info | list | count == 1 + +- name: Gather hcloud_storage_box_subaccount_info with wrong name + hetzner.hcloud.storage_box_subaccount_info: + storage_box: "{{ test_storage_box.hcloud_storage_box.id }}" + name: "{{ test_storage_box_subaccount1.hcloud_storage_box_subaccount.name }}-invalid" + register: result +- name: Verify hcloud_storage_box_subaccount_info with wrong name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_subaccount_info | list | count == 0 diff --git a/tests/integration/targets/storage_box_type_info/aliases b/tests/integration/targets/storage_box_type_info/aliases new file mode 100644 index 0000000..18b1111 --- /dev/null +++ b/tests/integration/targets/storage_box_type_info/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +gather_facts/no +azp/group2 diff --git a/tests/integration/targets/storage_box_type_info/defaults/main/common.yml b/tests/integration/targets/storage_box_type_info/defaults/main/common.yml new file mode 100644 index 0000000..015c3b5 --- /dev/null +++ b/tests/integration/targets/storage_box_type_info/defaults/main/common.yml @@ -0,0 +1,35 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('batch', 2) | map('first') | flatten() | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" + +# Used to easily update the server types and images across all our tests. +hcloud_server_type_name: cax11 +hcloud_server_type_id: 45 + +hcloud_server_type_upgrade_name: cax21 +hcloud_server_type_upgrade_id: 93 + +hcloud_image_name: debian-12 +hcloud_image_id: 114690389 # architecture=arm + +hcloud_location_name: hel1 +hcloud_location_id: 3 +hcloud_datacenter_name: hel1-dc2 +hcloud_datacenter_id: 3 + +hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/storage_box_type_info/defaults/main/main.yml b/tests/integration/targets/storage_box_type_info/defaults/main/main.yml new file mode 100644 index 0000000..57f246a --- /dev/null +++ b/tests/integration/targets/storage_box_type_info/defaults/main/main.yml @@ -0,0 +1,3 @@ +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- diff --git a/tests/integration/targets/storage_box_type_info/tasks/main.yml b/tests/integration/targets/storage_box_type_info/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/storage_box_type_info/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/storage_box_type_info/tasks/test.yml b/tests/integration/targets/storage_box_type_info/tasks/test.yml new file mode 100644 index 0000000..c68cd50 --- /dev/null +++ b/tests/integration/targets/storage_box_type_info/tasks/test.yml @@ -0,0 +1,64 @@ +# Copyright: (c) 2025, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Gather hcloud_storage_box_type_info + hetzner.hcloud.storage_box_type_info: + register: result +- name: Verify hcloud_storage_box_type_info + ansible.builtin.assert: + that: + - result.hcloud_storage_box_type_info | list | count >= 1 + +- name: Gather hcloud_storage_box_type_info in check mode + hetzner.hcloud.storage_box_type_info: + check_mode: true + register: result +- name: Verify hcloud_storage_box_type_info in check mode + ansible.builtin.assert: + that: + - result.hcloud_storage_box_type_info | list | count >= 1 + +- name: Gather hcloud_storage_box_type_info with correct id + hetzner.hcloud.storage_box_type_info: + id: "{{ hcloud_storage_box_type_id }}" + register: result +- name: Verify hcloud_storage_box_type_info with correct id + ansible.builtin.assert: + that: + - result.hcloud_storage_box_type_info | list | count == 1 + - result.hcloud_storage_box_type_info[0].id == hcloud_storage_box_type_id + - result.hcloud_storage_box_type_info[0].name == hcloud_storage_box_type_name + - result.hcloud_storage_box_type_info[0].description == "BX11" + - result.hcloud_storage_box_type_info[0].snapshot_limit == 10 + - result.hcloud_storage_box_type_info[0].automatic_snapshot_limit == 10 + - result.hcloud_storage_box_type_info[0].subaccounts_limit == 100 + - result.hcloud_storage_box_type_info[0].size == 1099511627776 + - result.hcloud_storage_box_type_info[0].deprecation is none + +- name: Gather hcloud_storage_box_type_info with wrong id + hetzner.hcloud.storage_box_type_info: + id: "{{ hcloud_storage_box_type_id }}4321" + ignore_errors: true + register: result +- name: Verify hcloud_storage_box_type_info with wrong id + ansible.builtin.assert: + that: + - result is failed + +- name: Gather hcloud_storage_box_type_info with correct name + hetzner.hcloud.storage_box_type_info: + name: "{{ hcloud_storage_box_type_name }}" + register: result +- name: Verify hcloud_storage_box_type_info with correct name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_type_info | list | count == 1 + +- name: Gather hcloud_storage_box_type_info with wrong name + hetzner.hcloud.storage_box_type_info: + name: "{{ hcloud_storage_box_type_name }}-invalid" + register: result +- name: Verify hcloud_storage_box_type_info with wrong name + ansible.builtin.assert: + that: + - result.hcloud_storage_box_type_info | list | count == 0 diff --git a/tests/integration/targets/subnetwork/defaults/main/common.yml b/tests/integration/targets/subnetwork/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/subnetwork/defaults/main/common.yml +++ b/tests/integration/targets/subnetwork/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/volume/defaults/main/common.yml b/tests/integration/targets/volume/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/volume/defaults/main/common.yml +++ b/tests/integration/targets/volume/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/volume_attachment/defaults/main/common.yml b/tests/integration/targets/volume_attachment/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/volume_attachment/defaults/main/common.yml +++ b/tests/integration/targets/volume_attachment/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/volume_info/defaults/main/common.yml b/tests/integration/targets/volume_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/volume_info/defaults/main/common.yml +++ b/tests/integration/targets/volume_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/zone/defaults/main/common.yml b/tests/integration/targets/zone/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/zone/defaults/main/common.yml +++ b/tests/integration/targets/zone/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/zone_info/defaults/main/common.yml b/tests/integration/targets/zone_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/zone_info/defaults/main/common.yml +++ b/tests/integration/targets/zone_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/zone_rrset/defaults/main/common.yml b/tests/integration/targets/zone_rrset/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/zone_rrset/defaults/main/common.yml +++ b/tests/integration/targets/zone_rrset/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/integration/targets/zone_rrset_info/defaults/main/common.yml b/tests/integration/targets/zone_rrset_info/defaults/main/common.yml index 0b15142..015c3b5 100644 --- a/tests/integration/targets/zone_rrset_info/defaults/main/common.yml +++ b/tests/integration/targets/zone_rrset_info/defaults/main/common.yml @@ -27,3 +27,9 @@ hcloud_datacenter_name: hel1-dc2 hcloud_datacenter_id: 3 hcloud_network_zone_name: eu-central + +hcloud_storage_box_type_name: bx11 +hcloud_storage_box_type_id: 1333 + +hcloud_storage_box_type_upgrade_name: bx21 +hcloud_storage_box_type_upgrade_id: 1334 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index af891af..6ec8537 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -11,5 +11,6 @@ def module(): obj.params = { "api_token": "dummy", "api_endpoint": "https://api.hetzner.cloud/v1", + "api_endpoint_hetzner": "https://api.hetzner.com/v1", } return obj