diff --git a/plugins/modules/volume_attachment.py b/plugins/modules/volume_attachment.py index 1f5599e..9a0ab2e 100644 --- a/plugins/modules/volume_attachment.py +++ b/plugins/modules/volume_attachment.py @@ -55,7 +55,6 @@ EXAMPLES = """ - name: Detach my-volume from my-server hetzner.hcloud.volume_attachment: volume: my-volume - server: my-server state: absent - name: Attach my-volume using id to my-server with automount enabled @@ -92,25 +91,33 @@ from ..module_utils.vendor.hcloud.servers import BoundServer from ..module_utils.vendor.hcloud.volumes import BoundVolume -class AnsibleHcloudVolumeAttachment(AnsibleHCloud): +class AnsibleHCloudVolumeAttachment(AnsibleHCloud): represent = "hcloud_volume_attachment" - hcloud_volume: BoundVolume | None = None + # We must the hcloud_volume_attachment name instead of hcloud_volume, because + # AnsibleHCloud.get_result does funny things. + hcloud_volume_attachment: BoundVolume | None = None hcloud_server: BoundServer | None = None def _prepare_result(self): return { - "volume": self.hcloud_volume.name, - "server": self.hcloud_volume.server.name if self.hcloud_volume.server is not None else None, + "volume": self.hcloud_volume_attachment.name, + "server": ( + self.hcloud_volume_attachment.server.name if self.hcloud_volume_attachment.server is not None else None + ), } - def _get_server_and_volume(self): + def _get_volume(self): try: - self.hcloud_volume = self._client_get_by_name_or_id( + self.hcloud_volume_attachment = self._client_get_by_name_or_id( "volumes", self.module.params.get("volume"), ) + except HCloudException as exception: + self.fail_json_hcloud(exception) + def _get_server(self): + try: self.hcloud_server = self._client_get_by_name_or_id( "servers", self.module.params.get("server"), @@ -119,41 +126,46 @@ class AnsibleHcloudVolumeAttachment(AnsibleHCloud): self.fail_json_hcloud(exception) def attach_volume(self): - try: - self._get_server_and_volume() + self.module.fail_on_missing_params(required_params=["server"]) - if self.hcloud_volume.server is not None: - if self.hcloud_volume.server.id == self.hcloud_server.id: + try: + self._get_volume() + self._get_server() + + if self.hcloud_volume_attachment.server is not None: + if self.hcloud_volume_attachment.server.id == self.hcloud_server.id: return if not self.module.check_mode: - action = self.hcloud_volume.detach() + action = self.hcloud_volume_attachment.detach() action.wait_until_finished() - self.hcloud_volume.server = None + self.hcloud_volume_attachment.server = None self._mark_as_changed() - else: - if not self.module.check_mode: - action = self.hcloud_volume.attach( - server=self.hcloud_server, - automount=self.module.params.get("automount"), - ) - action.wait_until_finished() + if not self.module.check_mode: + action = self.hcloud_volume_attachment.attach( + server=self.hcloud_server, + automount=self.module.params.get("automount"), + ) + action.wait_until_finished() - self.hcloud_volume.server = self.hcloud_server - self._mark_as_changed() + self.hcloud_volume_attachment.server = self.hcloud_server + self._mark_as_changed() except HCloudException as exception: self.fail_json_hcloud(exception) def detach_volume(self): try: - self._get_server_and_volume() - if self.hcloud_volume.server is not None: + self._get_volume() + + if self.hcloud_volume_attachment.server is not None: if not self.module.check_mode: - action = self.hcloud_volume.detach() + action = self.hcloud_volume_attachment.detach() action.wait_until_finished() + + self.hcloud_volume_attachment.server = None self._mark_as_changed() except HCloudException as exception: self.fail_json_hcloud(exception) @@ -163,8 +175,8 @@ class AnsibleHcloudVolumeAttachment(AnsibleHCloud): return AnsibleModule( argument_spec=dict( volume={"type": "str", "required": True}, - server={"type": "str", "required": True}, - automount={"type": "bool", "default": False}, + server={"type": "str"}, + automount={"type": "bool"}, state={ "choices": ["present", "absent"], "default": "present", @@ -176,9 +188,9 @@ class AnsibleHcloudVolumeAttachment(AnsibleHCloud): def main(): - module = AnsibleHcloudVolumeAttachment.define_module() + module = AnsibleHCloudVolumeAttachment.define_module() - hcloud = AnsibleHcloudVolumeAttachment(module) + hcloud = AnsibleHCloudVolumeAttachment(module) state = module.params["state"] if state == "present": hcloud.attach_volume() diff --git a/tests/integration/targets/volume_attachment/tasks/cleanup.yml b/tests/integration/targets/volume_attachment/tasks/cleanup.yml index 5f91342..69717f4 100644 --- a/tests/integration/targets/volume_attachment/tasks/cleanup.yml +++ b/tests/integration/targets/volume_attachment/tasks/cleanup.yml @@ -4,6 +4,12 @@ name: "{{ hcloud_server_name }}" state: absent +- name: Cleanup test_server2 + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}2" + state: absent + register: test_server2 + - name: Cleanup test_volume hetzner.hcloud.volume: name: "{{ hcloud_volume_name }}" diff --git a/tests/integration/targets/volume_attachment/tasks/prepare.yml b/tests/integration/targets/volume_attachment/tasks/prepare.yml new file mode 100644 index 0000000..913bfab --- /dev/null +++ b/tests/integration/targets/volume_attachment/tasks/prepare.yml @@ -0,0 +1,25 @@ +--- +- name: Create test_server + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + server_type: "{{ hcloud_server_type_name }}" + image: "{{ hcloud_image_name }}" + location: "{{ hcloud_location_name }}" + state: created + register: test_server + +- name: Create test_server2 + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}2" + server_type: "{{ hcloud_server_type_name }}" + image: "{{ hcloud_image_name }}" + location: "{{ hcloud_location_name }}" + state: created + register: test_server2 + +- name: Create test_volume + hetzner.hcloud.volume: + name: "{{ hcloud_volume_name }}" + size: 10 + location: "{{ hcloud_location_name }}" + register: test_volume diff --git a/tests/integration/targets/volume_attachment/tasks/test.yml b/tests/integration/targets/volume_attachment/tasks/test.yml index 00912e3..0223d2b 100644 --- a/tests/integration/targets/volume_attachment/tasks/test.yml +++ b/tests/integration/targets/volume_attachment/tasks/test.yml @@ -1,186 +1,128 @@ # 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: Setup server - hetzner.hcloud.server: - name: "{{ hcloud_server_name }}" - server_type: "{{ hcloud_server_type_name }}" - image: "{{ hcloud_image_name }}" - state: created - location: "{{ hcloud_location_name }}" - register: vol_server -- name: Verify setup server - ansible.builtin.assert: - that: - - vol_server is changed - -- name: Setup volume - hetzner.hcloud.volume: - name: "{{ hcloud_volume_name }}" - size: 10 - location: "{{ hcloud_location_name }}" - register: vol_volume -- name: Verify setup volume - ansible.builtin.assert: - that: - - vol_volume is changed - -- name: Test missing volume name # noqa: args[module] +- name: Test missing required parameters # noqa: args[module] hetzner.hcloud.volume_attachment: server: "{{ hcloud_server_name }}" register: result ignore_errors: true -- name: Verify fail test volume name or id +- name: Verify missing required parameters ansible.builtin.assert: that: - result is failed - 'result.msg == "missing required arguments: volume"' -- name: Test missing server name # noqa: args[module] +- name: Test missing required parameters # noqa: args[module] hetzner.hcloud.volume_attachment: volume: "{{ hcloud_volume_name }}" register: result ignore_errors: true -- name: Verify fail test server name +- name: Verify missing required parameters ansible.builtin.assert: that: - result is failed - 'result.msg == "missing required arguments: server"' -- name: Test attach Volume with check mode (Volume) +- name: Test attach with check mode hetzner.hcloud.volume_attachment: volume: "{{ hcloud_volume_name }}" server: "{{ hcloud_server_name }}" register: result check_mode: true -- name: Verify attach Volume with check mode result (Volume) +- name: Verify attach with check mode ansible.builtin.assert: that: - result is changed -- name: Test attach Volume (Volume) +- name: Test attach volume not found hetzner.hcloud.volume_attachment: - volume: "{{ hcloud_volume_name }}" - server: "{{ hcloud_server_name }}" - register: volume -- name: Verify test attach Volume (Volume) - ansible.builtin.assert: - that: - - volume is changed - - volume.hcloud_volume_attachment.volume == hcloud_volume_name - - volume.hcloud_volume_attachment.server == hcloud_server_name - -- name: Test attach Volume idempotence (Volume) - hetzner.hcloud.volume_attachment: - volume: "{{ hcloud_volume_name }}" - server: "{{ hcloud_server_name }}" - register: volume -- name: Verify test create Volume (Volume) - ansible.builtin.assert: - that: - - volume is not changed - -- name: Test detach Volume with checkmode (Volume) - hetzner.hcloud.volume_attachment: - volume: "{{ hcloud_volume_name }}" - server: "{{ hcloud_server_name }}" - state: "absent" - check_mode: true - register: volume -- name: Verify detach Volume with checkmode (Volume) - ansible.builtin.assert: - that: - - volume is changed - - volume.hcloud_volume_attachment.server == hcloud_server_name - -- name: Test detach Volume (Volume) - hetzner.hcloud.volume_attachment: - volume: "{{ hcloud_volume_name }}" - server: "{{ hcloud_server_name }}" - state: "absent" - register: volume -- name: Verify detach volume (Volume) - ansible.builtin.assert: - that: - - volume is changed - - volume.hcloud_volume_attachment.server == hcloud_server_name - -- name: Test detach Volume idempotency (Volume) - hetzner.hcloud.volume_attachment: - volume: "{{ hcloud_volume_name }}" - server: "{{ hcloud_server_name }}" - state: "absent" - register: volume -- name: Verify detach volume idempotency (Volume) - ansible.builtin.assert: - that: - - volume is not changed - -- name: Test attach Volume with check mode (ID) - hetzner.hcloud.volume_attachment: - volume: "{{ vol_volume.hcloud_volume.id }}" + volume: "invalid" server: "{{ hcloud_server_name }}" register: result - check_mode: true -- name: Verify attach Volume with check mode result (ID) + ignore_errors: true +- name: Verify attach volume not found + ansible.builtin.assert: + that: + - result is failed + - 'result.msg == "resource (volume) does not exist: invalid"' + +- name: Test attach server not found + hetzner.hcloud.volume_attachment: + volume: "{{ hcloud_volume_name }}" + server: "invalid" + register: result + ignore_errors: true +- name: Verify attach server not found + ansible.builtin.assert: + that: + - result is failed + - 'result.msg == "resource (server) does not exist: invalid"' + +- name: Test attach + hetzner.hcloud.volume_attachment: + volume: "{{ hcloud_volume_name }}" + server: "{{ hcloud_server_name }}" + register: result +- name: Verify attach ansible.builtin.assert: that: - result is changed + - result.hcloud_volume_attachment.volume == hcloud_volume_name + - result.hcloud_volume_attachment.server == hcloud_server_name -- name: Test attach Volume (ID) +- name: Test attach idempotency hetzner.hcloud.volume_attachment: - volume: "{{ vol_volume.hcloud_volume.id }}" + volume: "{{ hcloud_volume_name }}" server: "{{ hcloud_server_name }}" - register: volume -- name: Verify test attach Volume (ID) + register: result +- name: Verify attach idempotency ansible.builtin.assert: that: - - volume is changed - - volume.hcloud_volume_attachment.volume == hcloud_volume_name - - volume.hcloud_volume_attachment.server == hcloud_server_name + - result is not changed -- name: Test attach Volume idempotence (ID) +- name: Test attach to another server hetzner.hcloud.volume_attachment: - volume: "{{ vol_volume.hcloud_volume.id }}" - server: "{{ hcloud_server_name }}" - register: volume -- name: Verify test create Volume (ID) + volume: "{{ hcloud_volume_name }}" + server: "{{ hcloud_server_name }}2" + register: result +- name: Verify attach ansible.builtin.assert: that: - - volume is not changed + - result is changed + - result.hcloud_volume_attachment.volume == hcloud_volume_name + - result.hcloud_volume_attachment.server == hcloud_server_name + '2' -- name: Test detach Volume with checkmode (ID) +- name: Test detach with check mode hetzner.hcloud.volume_attachment: - volume: "{{ vol_volume.hcloud_volume.id }}" - server: "{{ hcloud_server_name }}" + volume: "{{ hcloud_volume_name }}" state: "absent" check_mode: true - register: volume -- name: Verify detach Volume with checkmode (ID) + register: result +- name: Verify detach with check mode ansible.builtin.assert: that: - - volume is changed - - volume.hcloud_volume_attachment.server == hcloud_server_name + - result is changed + - result.hcloud_volume_attachment.volume == hcloud_volume_name + - result.hcloud_volume_attachment.server is none -- name: Test detach Volume (ID) +- name: Test detach hetzner.hcloud.volume_attachment: - volume: "{{ vol_volume.hcloud_volume.id }}" - server: "{{ hcloud_server_name }}" + volume: "{{ hcloud_volume_name }}" state: "absent" - register: volume -- name: Verify detach volume (ID) + register: result +- name: Verify detach ansible.builtin.assert: that: - - volume is changed - - volume.hcloud_volume_attachment.server == hcloud_server_name + - result is changed + - result.hcloud_volume_attachment.volume == hcloud_volume_name + - result.hcloud_volume_attachment.server is none -- name: Test detach Volume idempotency (ID) +- name: Test detach idempotency hetzner.hcloud.volume_attachment: - volume: "{{ vol_volume.hcloud_volume.id }}" - server: "{{ hcloud_server_name }}" + volume: "{{ hcloud_volume_name }}" state: "absent" - register: volume -- name: Verify detach volume idempotency (ID) + register: result +- name: Verify detach idempotency ansible.builtin.assert: that: - - volume is not changed + - result is not changed