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

feat: support Storage Box Subaccount modules (#755)

##### SUMMARY

Add support for the Storage Box Snapshot resource with 2 new modules:

- `storage_box_subaccount`
- `storage_box_subaccount_info`

---------

Co-authored-by: Julian Tölle <julian.toelle@hetzner-cloud.de>
This commit is contained in:
Jonas L. 2025-12-10 11:32:23 +01:00 committed by GitHub
parent fd51b98cc8
commit 6dd76f7bde
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1249 additions and 4 deletions

View file

@ -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(),
}

View file

@ -40,14 +40,14 @@ extends_documentation_fragment:
EXAMPLES = """
- name: Gather all Storage Boxes
hetzner.hcloud.ssh_key_info:
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.ssh_key_info:
hetzner.hcloud.storage_box_info:
label_selector: env=prod
register: output
- name: Print the gathered infos
@ -55,7 +55,7 @@ EXAMPLES = """
var: output.hcloud_storage_box_info
- name: Gather a Storage Box by name
hetzner.hcloud.ssh_key_info:
hetzner.hcloud.storage_box_info:
name: backups
register: output
- name: Print the gathered infos
@ -63,7 +63,7 @@ EXAMPLES = """
var: output.hcloud_storage_box_info[0]
- name: Gather a Storage Box by id
hetzner.hcloud.ssh_key_info:
hetzner.hcloud.storage_box_info:
name: 12345
register: output
- name: Print the gathered infos

View file

@ -0,0 +1,473 @@
#!/usr/bin/python
# Copyright: (c) 2025, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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.
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.
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: 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"
"""
import string
from ..module_utils import storage_box, storage_box_subaccount
from ..module_utils.client import client_resource_not_found
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 _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
self.actions.append(resp.action)
self._wait_actions()
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)
self.actions.append(action)
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)
self.actions.append(action)
need_reload = True
self._mark_as_changed()
if not self.module.check_mode:
self._wait_actions()
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
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"))
self.actions.append(action)
self._wait_actions()
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()

View file

@ -0,0 +1,234 @@
#!/usr/bin/python
# Copyright: (c) 2025, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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.
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.
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.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 _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()

View file

@ -0,0 +1,3 @@
cloud/hcloud
gather_facts/no
azp/group2

View file

@ -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

View file

@ -0,0 +1,6 @@
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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-=)

View file

@ -0,0 +1,3 @@
---
dependencies:
- setup_ssh_keypair

View file

@ -0,0 +1,5 @@
---
- name: Cleanup test_storage_box
hetzner.hcloud.storage_box:
name: "{{ hcloud_storage_box_name }}"
state: absent

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,3 @@
cloud/hcloud
gather_facts/no
azp/group2

View file

@ -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

View file

@ -0,0 +1,6 @@
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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-=)

View file

@ -0,0 +1,3 @@
---
dependencies:
- setup_ssh_keypair

View file

@ -0,0 +1,5 @@
---
- name: Cleanup test_storage_box
hetzner.hcloud.storage_box:
name: "{{ hcloud_storage_box_name }}"
state: absent

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,86 @@
# Copyright: (c) 2025, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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