From 8eb46acbb118494b6855f0117df33ba1ec74f020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20K=C3=A4mmerling?= <4281581+LKaemmerling@users.noreply.github.com> Date: Fri, 18 Oct 2019 23:28:12 +0200 Subject: [PATCH] hcloud: Add Delete Protection to hcloud_volume and hcloud_volume_info (#63665) --- .../modules/cloud/hcloud/hcloud_volume.py | 115 +++++++++++------- .../cloud/hcloud/hcloud_volume_info.py | 32 +++-- .../targets/hcloud_volume/tasks/main.yml | 103 ++++++++++++---- 3 files changed, 168 insertions(+), 82 deletions(-) diff --git a/lib/ansible/modules/cloud/hcloud/hcloud_volume.py b/lib/ansible/modules/cloud/hcloud/hcloud_volume.py index 359852c9eb1..db141b264dc 100644 --- a/lib/ansible/modules/cloud/hcloud/hcloud_volume.py +++ b/lib/ansible/modules/cloud/hcloud/hcloud_volume.py @@ -18,12 +18,12 @@ DOCUMENTATION = """ --- module: hcloud_volume -short_description: Create and manage block volumes on the Hetzner Cloud. +short_description: Create and manage block Volume on the Hetzner Cloud. version_added: "2.8" description: - - Create, update and attach/detach block volumes on the Hetzner Cloud. + - Create, update and attach/detach block Volume on the Hetzner Cloud. author: - Christopher Schmitt (@cschmitt-hcloud) @@ -64,13 +64,18 @@ options: - Server Name the Volume should be assigned to. - Required if no I(location) is given and Volume does not exists. type: str + delete_protection: + description: + - Protect the Volume for deletion. + type: bool + version_added: "2.10" labels: description: - User-defined key-value pairs. type: dict state: description: - - State of the volume. + - State of the Volume. default: present choices: [absent, present] type: str @@ -78,32 +83,32 @@ extends_documentation_fragment: hcloud """ EXAMPLES = """ -- name: Create a volume +- name: Create a Volume hcloud_volume: name: my-volume location: fsn1 size: 100 state: present -- name: Create a volume and format it with ext4 +- name: Create a Volume and format it with ext4 hcloud_volume: name: my-volume location: fsn format: ext4 size: 100 state: present -- name: Mount a existing volume and automount +- name: Mount a existing Volume and automount hcloud_volume: name: my-volume server: my-server automount: yes state: present -- name: Mount a existing volume and automount +- name: Mount a existing Volume and automount hcloud_volume: name: my-volume server: my-server automount: yes state: present -- name: Ensure the volume is absent (remove if needed) +- name: Ensure the Volume is absent (remove if needed) hcloud_volume: name: my-volume state: absent @@ -111,33 +116,33 @@ EXAMPLES = """ RETURN = """ hcloud_volume: - description: The block volume + description: The block Volume returned: Always type: complex contains: id: - description: ID of the volume + description: ID of the Volume type: int returned: Always sample: 12345 name: - description: Name of the volume + description: Name of the Volume type: str returned: Always sample: my-volume size: - description: Size in GB of the volume + description: Size in GB of the Volume type: int returned: Always sample: 1337 linux_device: - description: Path to the device that contains the volume. + description: Path to the device that contains the Volume. returned: always type: str sample: /dev/disk/by-id/scsi-0HC_Volume_12345 version_added: "2.10" location: - description: Location name where the volume is located at + description: Location name where the Volume is located at type: str returned: Always sample: "fsn1" @@ -149,10 +154,16 @@ hcloud_volume: key: value mylabel: 123 server: - description: Server name where the volume is attached to + 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: "2.10" """ from ansible.module_utils.basic import AnsibleModule @@ -185,6 +196,7 @@ class AnsibleHcloudVolume(Hcloud): "labels": self.hcloud_volume.labels, "server": to_native(server_name), "linux_device": to_native(self.hcloud_volume.linux_device), + "delete_protection": self.hcloud_volume.protection["delete"], } def _get_volume(self): @@ -227,36 +239,45 @@ class AnsibleHcloudVolume(Hcloud): self._get_volume() def _update_volume(self): - 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") + 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() - 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: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_volume.labels: if not self.module.check_mode: - automount = self.module.params.get("automount", False) - self.hcloud_volume.attach(server, automount=automount).wait_until_finished() + self.hcloud_volume.update(labels=labels) self._mark_as_changed() - else: - if self.hcloud_volume.server is not None: + + 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.detach().wait_until_finished() + self.hcloud_volume.change_protection(delete=delete_protection).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() - - self._get_volume() + self._get_volume() + except hcloud.APIException as e: + self.module.fail_json(msg=e.message) def present_volume(self): self._get_volume() @@ -266,12 +287,15 @@ class AnsibleHcloudVolume(Hcloud): self._update_volume() def delete_volume(self): - self._get_volume() - if self.hcloud_volume is not None: - if not self.module.check_mode: - self.client.volumes.delete(self.hcloud_volume) - self._mark_as_changed() - self.hcloud_volume = None + try: + self._get_volume() + if self.hcloud_volume is not None: + if not self.module.check_mode: + self.client.volumes.delete(self.hcloud_volume) + self._mark_as_changed() + self.hcloud_volume = None + except hcloud.APIException as e: + self.module.fail_json(msg=e.message) @staticmethod def define_module(): @@ -287,6 +311,7 @@ class AnsibleHcloudVolume(Hcloud): format={"type": "str", "choices": ['xfs', 'ext4'], }, + delete_protection={"type": "bool"}, state={ "choices": ["absent", "present"], "default": "present", diff --git a/lib/ansible/modules/cloud/hcloud/hcloud_volume_info.py b/lib/ansible/modules/cloud/hcloud/hcloud_volume_info.py index 92e19fd5fb3..3dce298584a 100644 --- a/lib/ansible/modules/cloud/hcloud/hcloud_volume_info.py +++ b/lib/ansible/modules/cloud/hcloud/hcloud_volume_info.py @@ -18,11 +18,11 @@ DOCUMENTATION = """ --- module: hcloud_volume_info -short_description: Gather infos about your Hetzner Cloud volumes. +short_description: Gather infos about your Hetzner Cloud Volumes. version_added: "2.8" description: - - Gather infos about your Hetzner Cloud volumes. + - Gather infos about your Hetzner Cloud Volumes. author: - Lukas Kaemmerling (@LKaemmerling) @@ -30,21 +30,21 @@ author: options: id: description: - - The ID of the volume you want to get. + - The ID of the Volume you want to get. type: int name: description: - - The name of the volume you want to get. + - The name of the Volume you want to get. type: str label_selector: description: - - The label selector for the volume you want to get. + - The label selector for the Volume you want to get. type: str extends_documentation_fragment: hcloud """ EXAMPLES = """ -- name: Gather hcloud volume infos +- name: Gather hcloud Volume infos hcloud_volume_info: register: output - name: Print the gathered infos @@ -54,41 +54,46 @@ EXAMPLES = """ RETURN = """ hcloud_volume_info: - description: The volume infos as list + description: The Volume infos as list returned: always type: complex contains: id: - description: Numeric identifier of the volume + description: Numeric identifier of the Volume returned: always type: int sample: 1937415 name: - description: Name of the volume + description: Name of the Volume returned: always type: str sample: my-volume size: - description: Size of the volume + description: Size of the Volume returned: always type: str sample: 10 linux_device: - description: Path to the device that contains the volume. + description: Path to the device that contains the Volume. returned: always type: str sample: /dev/disk/by-id/scsi-0HC_Volume_12345 version_added: "2.10" location: - description: Name of the location where the volume resides in + description: Name of the location where the Volume resides in returned: always type: str sample: fsn1 server: - description: Name of the server where the volume is attached to + description: Name of the server where the Volume is attached to returned: always type: str sample: my-server + delete_protection: + description: True if the Volume is protected for deletion + returned: always + type: bool + version_added: "2.10" labels: description: User-defined labels (key-value pairs) returned: always @@ -126,6 +131,7 @@ class AnsibleHcloudVolumeInfo(Hcloud): "labels": volume.labels, "server": to_native(server_name), "linux_device": to_native(volume.linux_device), + "delete_protection": volume.protection["delete"], }) return tmp diff --git a/test/integration/targets/hcloud_volume/tasks/main.yml b/test/integration/targets/hcloud_volume/tasks/main.yml index d949b5cadd6..78b9b4041ed 100644 --- a/test/integration/targets/hcloud_volume/tasks/main.yml +++ b/test/integration/targets/hcloud_volume/tasks/main.yml @@ -26,25 +26,25 @@ - result is failed - 'result.msg == "missing required arguments: size"' -- name: test create volume with check mode +- name: test create Volume with check mode hcloud_volume: name: "{{hcloud_volume_name}}" size: 10 location: "fsn1" register: result check_mode: yes -- name: verify create volume with check mode result +- name: verify create Volume with check mode result assert: that: - result is changed -- name: test create volume +- name: test create Volume hcloud_volume: name: "{{hcloud_volume_name}}" size: 10 location: "fsn1" register: volume -- name: verify test create volume +- name: verify test create Volume assert: that: - volume is changed @@ -54,30 +54,30 @@ - volume.hcloud_volume.server != "{{hcloud_server_name}}" - volume.hcloud_volume.linux_device is defined -- name: test create volume idempotence +- name: test create Volume idempotence hcloud_volume: name: "{{hcloud_volume_name}}" size: 10 location: "fsn1" register: volume -- name: verify test create volume +- name: verify test create Volume assert: that: - volume is not changed -- name: test attach volume with checkmode +- name: test attach Volume with checkmode hcloud_volume: name: "{{hcloud_volume_name}}" server: "{{hcloud_server_name}}" check_mode: yes register: volume -- name: verify test attach volume with checkmode +- name: verify test attach Volume with checkmode assert: that: - volume is changed - volume.hcloud_volume.server != "{{hcloud_server_name}}" -- name: test attach volume +- name: test attach Volume hcloud_volume: name: "{{hcloud_volume_name}}" server: "{{hcloud_server_name}}" @@ -88,29 +88,29 @@ - volume is changed - volume.hcloud_volume.server == "{{hcloud_server_name}}" -- name: test attach volume idempotence +- name: test attach Volume idempotence hcloud_volume: name: "{{hcloud_volume_name}}" server: "{{hcloud_server_name}}" register: volume -- name: verify attach volume idempotence +- name: verify attach Volume idempotence assert: that: - volume is not changed - volume.hcloud_volume.server == "{{hcloud_server_name}}" -- name: test detach volume with checkmode +- name: test detach Volume with checkmode hcloud_volume: name: "{{hcloud_volume_name}}" check_mode: yes register: volume -- name: verify detach volume with checkmode +- name: verify detach Volume with checkmode assert: that: - volume is changed - volume.hcloud_volume.server == "{{hcloud_server_name}}" -- name: test detach volume +- name: test detach Volume hcloud_volume: name: "{{hcloud_volume_name}}" register: volume @@ -121,57 +121,112 @@ - volume.hcloud_volume.location == "fsn1" - volume.hcloud_volume.server != "{{hcloud_server_name}}" -- name: test update volume label +- name: test update Volume label hcloud_volume: name: "{{hcloud_volume_name}}" labels: key: value register: volume -- name: verify test update volume lable +- name: verify test update Volume label assert: that: - volume is changed - volume.hcloud_volume.labels.key == "value" -- name: test update volume label with the same label +- name: test update Volume label with the same label hcloud_volume: name: "{{hcloud_volume_name}}" labels: key: value register: volume -- name: verify test update volume lable with the same label +- name: verify test update Volume lable with the same label assert: that: - volume is not changed -- name: test increase volume size +- name: test increase Volume size hcloud_volume: name: "{{hcloud_volume_name}}" size: 11 register: volume -- name: verify test increase volume size +- name: verify test increase Volume size assert: that: - volume is changed - volume.hcloud_volume.size == 11 -- name: test decreace volume size +- name: test decreace Volume size hcloud_volume: name: "{{hcloud_volume_name}}" size: 10 register: volume -- name: verify test decreace volume size +- name: verify test decreace Volume size assert: that: - volume is not changed - volume.hcloud_volume.size == 11 -- name: test delete volume +- name: test update Volume delete protection + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: true + register: volume +- name: verify test update Volume delete protection + assert: + that: + - volume is changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test update Volume delete protection idempotency + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: true + register: volume +- name: verify test update Volume delete protection idempotency + assert: + that: + - volume is not changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test Volume without delete protection set to be idempotent + hcloud_volume: + name: "{{hcloud_volume_name}}" + register: volume +- name: verify test Volume without delete protection set to be idempotent + assert: + that: + - volume is not changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test delete Volume fails if it is protected + hcloud_volume: + name: "{{hcloud_volume_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Volume fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "volume deletion is protected"' + +- name: test update Volume delete protection + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: false + register: volume +- name: verify test update Volume delete protection + assert: + that: + - volume is changed + - volume.hcloud_volume.delete_protection is sameas false + +- name: test delete Volume hcloud_volume: name: "{{hcloud_volume_name}}" state: absent register: result -- name: verify delete volume +- name: verify delete Volume assert: that: - result is success