diff options
Diffstat (limited to 'ansible_collections/hetzner/hcloud/plugins/modules/volume.py')
-rw-r--r-- | ansible_collections/hetzner/hcloud/plugins/modules/volume.py | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/volume.py b/ansible_collections/hetzner/hcloud/plugins/modules/volume.py new file mode 100644 index 000000000..8442ed90b --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/volume.py @@ -0,0 +1,331 @@ +#!/usr/bin/python + +# 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) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: volume + +short_description: Create and manage block Volume on the Hetzner Cloud. + + +description: + - Create, update and attach/detach block Volume on the Hetzner Cloud. + +author: + - Christopher Schmitt (@cschmitt-hcloud) + +options: + id: + description: + - The ID of the Hetzner Cloud Block Volume to manage. + - Only required if no volume I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud Block Volume to manage. + - Only required if no volume I(id) is given or a volume does not exist. + type: str + size: + description: + - The size of the Block Volume in GB. + - Required if volume does not yet exists. + type: int + automount: + description: + - Automatically mount the Volume. + type: bool + default: False + format: + description: + - Automatically Format the volume on creation + - Can only be used in case the Volume does not exist. + type: str + choices: [xfs, ext4] + location: + description: + - Location of the Hetzner Cloud Volume. + - Required if no I(server) is given and Volume does not exist. + type: str + server: + description: + - Server Name the Volume should be assigned to. + - Required if no I(location) is given and Volume does not exist. + type: str + delete_protection: + description: + - Protect the Volume for deletion. + type: bool + labels: + description: + - User-defined key-value pairs. + type: dict + state: + description: + - State of the Volume. + default: present + choices: [absent, present] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Create a Volume + hetzner.hcloud.volume: + name: my-volume + location: fsn1 + size: 100 + state: present +- name: Create a Volume and format it with ext4 + hetzner.hcloud.volume: + name: my-volume + location: fsn + format: ext4 + size: 100 + state: present +- name: Mount a existing Volume and automount + hetzner.hcloud.volume: + name: my-volume + server: my-server + automount: true + state: present +- name: Mount a existing Volume and automount + hetzner.hcloud.volume: + name: my-volume + server: my-server + automount: true + state: present +- name: Ensure the Volume is absent (remove if needed) + hetzner.hcloud.volume: + name: my-volume + state: absent +""" + +RETURN = """ +hcloud_volume: + description: The block Volume + returned: Always + type: complex + contains: + id: + description: ID of the Volume + type: int + returned: Always + sample: 12345 + name: + description: Name of the Volume + type: str + returned: Always + sample: my-volume + size: + description: Size in GB of the Volume + type: int + returned: Always + sample: 1337 + linux_device: + description: Path to the device that contains the Volume. + returned: always + type: str + sample: /dev/disk/by-id/scsi-0HC_Volume_12345 + version_added: "0.1.0" + location: + description: Location name where the Volume is located at + type: str + returned: Always + sample: "fsn1" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 + server: + description: Server name where the Volume is attached to + type: str + returned: Always + sample: "my-server" + delete_protection: + description: True if Volume is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.volumes import BoundVolume + + +class AnsibleHCloudVolume(AnsibleHCloud): + represent = "hcloud_volume" + + hcloud_volume: BoundVolume | None = None + + def _prepare_result(self): + server_name = None + if self.hcloud_volume.server is not None: + server_name = to_native(self.hcloud_volume.server.name) + + return { + "id": to_native(self.hcloud_volume.id), + "name": to_native(self.hcloud_volume.name), + "size": self.hcloud_volume.size, + "location": to_native(self.hcloud_volume.location.name), + "labels": self.hcloud_volume.labels, + "server": server_name, + "linux_device": to_native(self.hcloud_volume.linux_device), + "delete_protection": self.hcloud_volume.protection["delete"], + } + + def _get_volume(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_volume = self.client.volumes.get_by_id(self.module.params.get("id")) + else: + self.hcloud_volume = self.client.volumes.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_volume(self): + self.module.fail_on_missing_params(required_params=["name", "size"]) + params = { + "name": self.module.params.get("name"), + "size": self.module.params.get("size"), + "automount": self.module.params.get("automount"), + "format": self.module.params.get("format"), + "labels": self.module.params.get("labels"), + } + if self.module.params.get("server") is not None: + params["server"] = self.client.servers.get_by_name(self.module.params.get("server")) + elif self.module.params.get("location") is not None: + params["location"] = self.client.locations.get_by_name(self.module.params.get("location")) + else: + self.module.fail_json(msg="server or location is required") + + if not self.module.check_mode: + try: + resp = self.client.volumes.create(**params) + resp.action.wait_until_finished() + [action.wait_until_finished() for action in resp.next_actions] + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_volume() + self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_volume() + + def _update_volume(self): + try: + size = self.module.params.get("size") + if size: + if self.hcloud_volume.size < size: + if not self.module.check_mode: + self.hcloud_volume.resize(size).wait_until_finished() + self._mark_as_changed() + elif self.hcloud_volume.size > size: + self.module.warn("Shrinking of volumes is not supported") + + server_name = self.module.params.get("server") + if server_name: + server = self.client.servers.get_by_name(server_name) + if self.hcloud_volume.server is None or self.hcloud_volume.server.name != server.name: + if not self.module.check_mode: + automount = self.module.params.get("automount", False) + self.hcloud_volume.attach(server, automount=automount).wait_until_finished() + self._mark_as_changed() + else: + if self.hcloud_volume.server is not None: + if not self.module.check_mode: + self.hcloud_volume.detach().wait_until_finished() + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_volume.labels: + if not self.module.check_mode: + self.hcloud_volume.update(labels=labels) + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_volume.protection["delete"]: + if not self.module.check_mode: + self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_volume() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def present_volume(self): + self._get_volume() + if self.hcloud_volume is None: + self._create_volume() + else: + self._update_volume() + + def delete_volume(self): + try: + self._get_volume() + if self.hcloud_volume is not None: + if not self.module.check_mode: + if self.hcloud_volume.server is not None: + self.hcloud_volume.detach().wait_until_finished() + self.client.volumes.delete(self.hcloud_volume) + self._mark_as_changed() + self.hcloud_volume = None + 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"}, + size={"type": "int"}, + location={"type": "str"}, + server={"type": "str"}, + labels={"type": "dict"}, + automount={"type": "bool", "default": False}, + format={"type": "str", "choices": ["xfs", "ext4"]}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + mutually_exclusive=[["location", "server"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudVolume.define_module() + + hcloud = AnsibleHCloudVolume(module) + state = module.params.get("state") + if state == "absent": + module.fail_on_missing_params(required_params=["name"]) + hcloud.delete_volume() + else: + hcloud.present_volume() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() |