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

add snapshot plan

This commit is contained in:
jo 2025-12-03 13:53:07 +01:00
parent 4c75cfc7e5
commit c5809b6892
No known key found for this signature in database
GPG key ID: B2FEC9B22722B984
4 changed files with 188 additions and 6 deletions

View file

@ -6,6 +6,7 @@
from __future__ import annotations
import traceback
from copy import deepcopy
from typing import Any, NoReturn
from ansible.module_utils.basic import AnsibleModule as AnsibleModuleBase, env_fallback
@ -29,6 +30,23 @@ from .version import version
# Provide typing definitions to the AnsibleModule class
class AnsibleModule(AnsibleModuleBase):
params: dict
params_raw: dict
def _load_params(self):
"""
Copy the params before validation, to keep track whether a value was defined by the user.
Validation will modify the params dict by adding missing keys.
"""
# https://github.com/ansible/ansible/blob/7b4d4ed672415f31689e7f25bc0b40c0697c0c88/lib/ansible/module_utils/basic.py#L1244-L1251
super()._load_params()
self.params_raw = deepcopy(self.params)
def param_is_defined(self, key: str):
"""
Check if a parameter was defined by the user.
"""
return key in self.params_raw
class AnsibleHCloud:

View file

@ -29,4 +29,15 @@ def prepare_result(o: BoundStorageBox):
"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,
}
),
}

View file

@ -85,6 +85,36 @@ options:
- 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
hour:
description:
- Hour when the Snapshot Plan is executed (UTC).
type: int
minute:
description:
- Minute when the Snapshot Plan is executed (UTC).
type: int
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.
@ -126,6 +156,24 @@ EXAMPLES = """
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: Delete a Storage Box
hetzner.hcloud.storage_box:
name: my-storage-box
@ -199,6 +247,36 @@ hcloud_storage_box:
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
@ -241,16 +319,15 @@ hcloud_storage_box:
sample: 10485760
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils import storage_box
from ..module_utils.hcloud import AnsibleHCloud
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,
StorageBoxSnapshotPlan,
)
@ -294,9 +371,19 @@ class AnsibleStorageBox(AnsibleHCloud):
self.storage_box = resp.storage_box
if not self.module.check_mode:
self._wait_actions()
if (value := self.module.params.get("delete_protection")) is not None:
action = self.storage_box.change_protection(delete=value)
self.actions.append(action)
if not self.module.check_mode:
action = self.storage_box.change_protection(delete=value)
self.actions.append(action)
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))
self.actions.append(action)
if not self.module.check_mode:
self._wait_actions()
@ -327,6 +414,24 @@ class AnsibleStorageBox(AnsibleHCloud):
self.actions.append(action)
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)
self.actions.append(action)
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()
self.actions.append(action)
self._mark_as_changed()
# self.storage_box.reset_password
params = {}
@ -397,6 +502,16 @@ class AnsibleStorageBox(AnsibleHCloud):
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"},
state={
"choices": ["absent", "present"],

View file

@ -39,6 +39,10 @@
access_settings:
ssh_enabled: true
zfs_enabled: false
snapshot_plan:
max_snapshots: 10
hour: 3
minute: 30
labels:
key: value
register: result
@ -57,6 +61,9 @@
- 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
@ -76,6 +83,10 @@
access_settings:
ssh_enabled: true
zfs_enabled: false
snapshot_plan:
max_snapshots: 10
hour: 3
minute: 30
labels:
key: value
register: result
@ -95,6 +106,11 @@
- 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 }}"
@ -106,6 +122,10 @@
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
@ -125,6 +145,9 @@
- 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:
@ -137,6 +160,10 @@
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
@ -163,12 +190,23 @@
name: "{{ hcloud_storage_box_name }}"
delete_protection: false
register: result
- name: Verify update primary delete protection
- 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 delete
hetzner.hcloud.storage_box:
name: "{{ hcloud_storage_box_name }}"