From 86e4dcbaedba396691c731b89865b7d850e24dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20K=C3=A4mmerling?= <4281581+LKaemmerling@users.noreply.github.com> Date: Sat, 19 Oct 2019 12:57:00 +0200 Subject: [PATCH] hcloud: Add protection support to hcloud_server and hcloud_server_info (#63669) --- .../modules/cloud/hcloud/hcloud_server.py | 198 +++++++++++------- .../cloud/hcloud/hcloud_server_info.py | 14 ++ .../targets/hcloud_server/tasks/main.yml | 92 ++++++++ 3 files changed, 232 insertions(+), 72 deletions(-) diff --git a/lib/ansible/modules/cloud/hcloud/hcloud_server.py b/lib/ansible/modules/cloud/hcloud/hcloud_server.py index 2dd0da4646f..5cb5211c24d 100644 --- a/lib/ansible/modules/cloud/hcloud/hcloud_server.py +++ b/lib/ansible/modules/cloud/hcloud/hcloud_server.py @@ -100,6 +100,18 @@ options: description: - User-defined labels (key-value pairs). type: dict + delete_protection: + description: + - Protect the Server for deletion. + - Needs to be the same as I(rebuild_protection). + type: bool + version_added: "2.10" + rebuild_protection: + description: + - Protect the Server for rebuild. + - Needs to be the same as I(delete_protection). + type: bool + version_added: "2.10" state: description: - State of the server. @@ -227,6 +239,18 @@ hcloud_server: description: User-defined labels (key-value pairs) returned: always type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "2.10" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "2.10" """ from ansible.module_utils.basic import AnsibleModule @@ -260,6 +284,8 @@ class AnsibleHcloudServer(Hcloud): "rescue_enabled": self.hcloud_server.rescue_enabled, "backup_window": to_native(self.hcloud_server.backup_window), "labels": self.hcloud_server.labels, + "delete_protection": self.hcloud_server.protection["delete"], + "rebuild_protection": self.hcloud_server.protection["rebuild"], "status": to_native(self.hcloud_server.status), } @@ -334,59 +360,72 @@ class AnsibleHcloudServer(Hcloud): self._get_server() def _update_server(self): - rescue_mode = self.module.params.get("rescue_mode") - if rescue_mode and self.hcloud_server.rescue_enabled is False: - if not self.module.check_mode: - self._set_rescue_mode(rescue_mode) - self._mark_as_changed() - elif not rescue_mode and self.hcloud_server.rescue_enabled is True: - if not self.module.check_mode: - self.hcloud_server.disable_rescue().wait_until_finished() - self._mark_as_changed() - - if self.module.params.get("backups") and self.hcloud_server.backup_window is None: - if not self.module.check_mode: - self.hcloud_server.enable_backup().wait_until_finished() - self._mark_as_changed() - elif not self.module.params.get("backups") and self.hcloud_server.backup_window is not None: - if not self.module.check_mode: - self.hcloud_server.disable_backup().wait_until_finished() - self._mark_as_changed() - - labels = self.module.params.get("labels") - if labels is not None and labels != self.hcloud_server.labels: - if not self.module.check_mode: - self.hcloud_server.update(labels=labels) - self._mark_as_changed() - - server_type = self.module.params.get("server_type") - if server_type is not None and self.hcloud_server.server_type.name != server_type: - previous_server_status = self.hcloud_server.status - state = self.module.params.get("state") - if previous_server_status == Server.STATUS_RUNNING: + try: + rescue_mode = self.module.params.get("rescue_mode") + if rescue_mode and self.hcloud_server.rescue_enabled is False: if not self.module.check_mode: - if self.module.params.get("force_upgrade") or state == "stopped": - self.stop_server() # Only stopped server can be upgraded - else: - self.module.warn( - "You can not upgrade a running instance %s. You need to stop the instance or use force_upgrade=yes." - % self.hcloud_server.name - ) - timeout = 100 - if self.module.params.get("upgrade_disk"): - timeout = ( - 1000 - ) # When we upgrade the disk too the resize progress takes some more time. - if not self.module.check_mode: - self.hcloud_server.change_type( - server_type=self.client.server_types.get_by_name(server_type), - upgrade_disk=self.module.params.get("upgrade_disk"), - ).wait_until_finished(timeout) - if state == "present" and previous_server_status == Server.STATUS_RUNNING or state == "started": - self.start_server() + self._set_rescue_mode(rescue_mode) + self._mark_as_changed() + elif not rescue_mode and self.hcloud_server.rescue_enabled is True: + if not self.module.check_mode: + self.hcloud_server.disable_rescue().wait_until_finished() + self._mark_as_changed() - self._mark_as_changed() - self._get_server() + if self.module.params.get("backups") and self.hcloud_server.backup_window is None: + if not self.module.check_mode: + self.hcloud_server.enable_backup().wait_until_finished() + self._mark_as_changed() + elif not self.module.params.get("backups") and self.hcloud_server.backup_window is not None: + if not self.module.check_mode: + self.hcloud_server.disable_backup().wait_until_finished() + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_server.labels: + if not self.module.check_mode: + self.hcloud_server.update(labels=labels) + self._mark_as_changed() + + server_type = self.module.params.get("server_type") + if server_type is not None and self.hcloud_server.server_type.name != server_type: + previous_server_status = self.hcloud_server.status + state = self.module.params.get("state") + if previous_server_status == Server.STATUS_RUNNING: + if not self.module.check_mode: + if self.module.params.get("force_upgrade") or state == "stopped": + self.stop_server() # Only stopped server can be upgraded + else: + self.module.warn( + "You can not upgrade a running instance %s. You need to stop the instance or use force_upgrade=yes." + % self.hcloud_server.name + ) + timeout = 100 + if self.module.params.get("upgrade_disk"): + timeout = ( + 1000 + ) # When we upgrade the disk too the resize progress takes some more time. + if not self.module.check_mode: + self.hcloud_server.change_type( + server_type=self.client.server_types.get_by_name(server_type), + upgrade_disk=self.module.params.get("upgrade_disk"), + ).wait_until_finished(timeout) + if state == "present" and previous_server_status == Server.STATUS_RUNNING or state == "started": + self.start_server() + + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + rebuild_protection = self.module.params.get("rebuild_protection") + if (delete_protection is not None and rebuild_protection is not None) and ( + delete_protection != self.hcloud_server.protection["delete"] or rebuild_protection != + self.hcloud_server.protection["rebuild"]): + if not self.module.check_mode: + self.hcloud_server.change_protection(delete=delete_protection, + rebuild=rebuild_protection).wait_until_finished() + self._mark_as_changed() + self._get_server() + except APIException as e: + self.module.fail_json(msg=e.message) def _set_rescue_mode(self, rescue_mode): if self.module.params.get("ssh_keys"): @@ -401,29 +440,38 @@ class AnsibleHcloudServer(Hcloud): self.result["root_password"] = resp.root_password def start_server(self): - if self.hcloud_server.status != Server.STATUS_RUNNING: - if not self.module.check_mode: - self.client.servers.power_on(self.hcloud_server).wait_until_finished() - self._mark_as_changed() - self._get_server() + try: + if self.hcloud_server.status != Server.STATUS_RUNNING: + if not self.module.check_mode: + self.client.servers.power_on(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self._get_server() + except APIException as e: + self.module.fail_json(msg=e.message) def stop_server(self): - if self.hcloud_server.status != Server.STATUS_OFF: - if not self.module.check_mode: - self.client.servers.power_off(self.hcloud_server).wait_until_finished() - self._mark_as_changed() - self._get_server() + try: + if self.hcloud_server.status != Server.STATUS_OFF: + if not self.module.check_mode: + self.client.servers.power_off(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self._get_server() + except APIException as e: + self.module.fail_json(msg=e.message) def rebuild_server(self): self.module.fail_on_missing_params( required_params=["image"] ) - if not self.module.check_mode: - self.client.servers.rebuild(self.hcloud_server, self.client.images.get_by_name( - self.module.params.get("image"))).wait_until_finished() - self._mark_as_changed() + try: + if not self.module.check_mode: + self.client.servers.rebuild(self.hcloud_server, self.client.images.get_by_name( + self.module.params.get("image"))).wait_until_finished() + self._mark_as_changed() - self._get_server() + self._get_server() + except APIException as e: + self.module.fail_json(msg=e.message) def present_server(self): self._get_server() @@ -433,12 +481,15 @@ class AnsibleHcloudServer(Hcloud): self._update_server() def delete_server(self): - self._get_server() - if self.hcloud_server is not None: - if not self.module.check_mode: - self.client.servers.delete(self.hcloud_server).wait_until_finished() - self._mark_as_changed() - self.hcloud_server = None + try: + self._get_server() + if self.hcloud_server is not None: + if not self.module.check_mode: + self.client.servers.delete(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self.hcloud_server = None + except APIException as e: + self.module.fail_json(msg=e.message) @staticmethod def define_module(): @@ -458,6 +509,8 @@ class AnsibleHcloudServer(Hcloud): upgrade_disk={"type": "bool", "default": False}, force_upgrade={"type": "bool", "default": False}, rescue_mode={"type": "str"}, + delete_protection={"type": "bool"}, + rebuild_protection={"type": "bool"}, state={ "choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"], "default": "present", @@ -466,6 +519,7 @@ class AnsibleHcloudServer(Hcloud): ), required_one_of=[['id', 'name']], mutually_exclusive=[["location", "datacenter"]], + required_together=[["delete_protection", "rebuild_protection"]], supports_check_mode=True, ) diff --git a/lib/ansible/modules/cloud/hcloud/hcloud_server_info.py b/lib/ansible/modules/cloud/hcloud/hcloud_server_info.py index 10f6b45a1cf..c369a56b120 100644 --- a/lib/ansible/modules/cloud/hcloud/hcloud_server_info.py +++ b/lib/ansible/modules/cloud/hcloud/hcloud_server_info.py @@ -116,6 +116,18 @@ hcloud_server_info: description: User-defined labels (key-value pairs) returned: always type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "2.10" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "2.10" """ from ansible.module_utils.basic import AnsibleModule @@ -151,6 +163,8 @@ class AnsibleHcloudServerInfo(Hcloud): "backup_window": to_native(server.backup_window), "labels": server.labels, "status": to_native(server.status), + "delete_protection": server.protection["delete"], + "rebuild_protection": server.protection["rebuild"], }) return tmp diff --git a/test/integration/targets/hcloud_server/tasks/main.yml b/test/integration/targets/hcloud_server/tasks/main.yml index 6910ce3a5b3..945df73020d 100644 --- a/test/integration/targets/hcloud_server/tasks/main.yml +++ b/test/integration/targets/hcloud_server/tasks/main.yml @@ -260,6 +260,98 @@ that: - result_after_test is changed +- name: test update server protection booth protection arguments are required + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection booth protection arguments are required + assert: + that: + - result_after_test is failed + - 'result_after_test.msg == "parameters are required together: delete_protection, rebuild_protection"' + +- name: test update server protection fails if they are not the same + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + rebuild_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection fails if they are not the same + assert: + that: + - result_after_test is failed + +- name: test update server protection + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + rebuild_protection: true + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_server.delete_protection is sameas true + - result_after_test.hcloud_server.rebuild_protection is sameas true + +- name: test server without protection set to be idempotent + hcloud_server: + name: "{{hcloud_server_name}}" + register: result_after_test +- name: verify test server without protection set to be idempotent + assert: + that: + - result_after_test is not changed + - result_after_test.hcloud_server.delete_protection is sameas true + - result_after_test.hcloud_server.rebuild_protection is sameas true + +- name: test delete server fails if it is protected + hcloud_server: + name: "{{hcloud_server_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete server fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "server deletion is protected"' + +- name: test rebuild server fails if it is protected + hcloud_server: + name: "{{hcloud_server_name}}" + image: ubuntu-18.04 + state: rebuild + ignore_errors: yes + register: result +- name: verify rebuild server fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "server rebuild is protected"' + +- name: test remove server protection + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: false + rebuild_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify remove server protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_server.delete_protection is sameas false + - result_after_test.hcloud_server.rebuild_protection is sameas false + - name: absent server hcloud_server: name: "{{ hcloud_server_name }}"