From 3c3a4771a7917676e20b0f0660d468c5b16ed4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20K=C3=A4mmerling?= Date: Thu, 11 Mar 2021 11:07:41 +0100 Subject: [PATCH] Implement Firewall Support (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lukas Kämmerling --- .gitignore | 2 + plugins/modules/hcloud_firewall.py | 331 ++++++++++++++++++ plugins/modules/hcloud_server.py | 51 ++- .../targets/hcloud_firewall/aliases | 2 + .../targets/hcloud_firewall/defaults/main.yml | 5 + .../targets/hcloud_firewall/meta/main.yml | 3 + .../targets/hcloud_firewall/tasks/main.yml | 169 +++++++++ .../targets/hcloud_server/defaults/main.yml | 1 + .../targets/hcloud_server/tasks/main.yml | 108 +++++- tests/utils/gitlab/sanity.sh | 2 +- 10 files changed, 658 insertions(+), 16 deletions(-) create mode 100644 plugins/modules/hcloud_firewall.py create mode 100644 tests/integration/targets/hcloud_firewall/aliases create mode 100644 tests/integration/targets/hcloud_firewall/defaults/main.yml create mode 100644 tests/integration/targets/hcloud_firewall/meta/main.yml create mode 100644 tests/integration/targets/hcloud_firewall/tasks/main.yml diff --git a/.gitignore b/.gitignore index c6fc14a..d6a37e5 100644 --- a/.gitignore +++ b/.gitignore @@ -385,3 +385,5 @@ $RECYCLE.BIN/ *.lnk # End of https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv + +cloud-config-hcloud.ini diff --git a/plugins/modules/hcloud_firewall.py b/plugins/modules/hcloud_firewall.py new file mode 100644 index 0000000..6c2b475 --- /dev/null +++ b/plugins/modules/hcloud_firewall.py @@ -0,0 +1,331 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_firewall + +short_description: Create and manage firewalls on the Hetzner Cloud. + + +description: + - Create, update and manage firewalls on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud firewall to manage. + - Only required if no firewall I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud firewall to manage. + - Only required if no firewall I(id) is given, or a firewall does not exists. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + rules: + description: + - List of rules the firewall should contain. + type: list + elements: dict + suboptions: + direction: + description: + - The direction of the firewall rule. + type: str + choices: [ in, out ] + port: + description: + - The port of the firewall rule. + type: str + protocol: + description: + - The protocol of the firewall rule. + type: str + choices: [ icmp, tcp, udp ] + source_ips: + description: + - List of CIDRs that are allowed within this rule + type: list + elements: str + destination_ips: + description: + - List of CIDRs that are allowed within this rule + type: list + elements: str + state: + description: + - State of the firewall. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud +''' + +EXAMPLES = """ +- name: Create a basic firewall + hcloud_firewall: + name: my-firewall + state: present + +- name: Create a firewall with rules + hcloud_firewall: + name: my-firewall + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + state: present + +- name: Create a firewall with labels + hcloud_firewall: + name: my-firewall + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the firewall is absent (remove if needed) + hcloud_firewall: + name: my-firewall + state: absent +""" + +RETURN = """ +hcloud_firewall: + description: The firewall instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the firewall + returned: always + type: int + sample: 1937415 + name: + description: Name of the firewall + returned: always + type: str + sample: my firewall + rules: + description: List of Rules within this Firewall + returned: always + type: complex + contains: + direction: + description: Direction of the Firewall Rule + type: str + returned: always + sample: in + protocol: + description: Protocol of the Firewall Rule + type: str + returned: always + sample: icmp + port: + description: Port of the Firewall Rule, None/Null if protocol is icmp + type: str + returned: always + sample: in + source_ips: + description: Source IPs of the Firewall + type: list + elements: str + returned: always + destination_ips: + description: Source IPs of the Firewall + type: list + elements: str + returned: always + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + +try: + from hcloud.firewalls.domain import FirewallRule + from hcloud import APIException +except ImportError: + APIException = None + FirewallRule = None + + +class AnsibleHcloudFirewall(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_firewall") + self.hcloud_firewall = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_firewall.id), + "name": to_native(self.hcloud_firewall.name), + "rules": [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules], + "labels": self.hcloud_firewall.labels + } + + def _prepare_result_rule(self, rule): + return { + "direction": rule.direction, + "protocol": to_native(rule.protocol), + "port": to_native(rule.port) if rule.port is not None else None, + "source_ips": [to_native(cidr) for cidr in rule.source_ips], + "destination_ips": [to_native(cidr) for cidr in rule.destination_ips] + } + + def _get_firewall(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_firewall = self.client.firewalls.get_by_id( + self.module.params.get("id") + ) + elif self.module.params.get("name") is not None: + self.hcloud_firewall = self.client.firewalls.get_by_name( + self.module.params.get("name") + ) + + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_firewall(self): + self.module.fail_on_missing_params( + required_params=["name"] + ) + params = { + "name": self.module.params.get("name"), + "labels": self.module.params.get("labels") + } + rules = self.module.params.get("rules") + if rules is not None: + params["rules"] = [ + FirewallRule( + direction=rule["direction"], + protocol=rule["protocol"], + source_ips=rule["source_ips"], + destination_ips=rule["destination_ips"], + port=rule["port"] + ) + for rule in rules + ] + if not self.module.check_mode: + try: + self.client.firewalls.create(**params) + except Exception as e: + self.module.fail_json(msg=e.message, **params) + self._mark_as_changed() + self._get_firewall() + + def _update_firewall(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_firewall.name != name: + self.module.fail_on_missing_params( + required_params=["id"] + ) + if not self.module.check_mode: + self.hcloud_firewall.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_firewall.labels != labels: + if not self.module.check_mode: + self.hcloud_firewall.update(labels=labels) + self._mark_as_changed() + + rules = self.module.params.get("rules") + if rules is not None and self.hcloud_firewall.rules != rules: + if not self.module.check_mode: + new_rules = [ + FirewallRule( + direction=rule["direction"], + protocol=rule["protocol"], + source_ips=rule["source_ips"], + destination_ips=rule["destination_ips"], + port=rule["port"] + ) + for rule in rules + ] + self.hcloud_firewall.set_rules(new_rules) + self._mark_as_changed() + self._get_firewall() + + def present_firewall(self): + self._get_firewall() + if self.hcloud_firewall is None: + self._create_firewall() + else: + self._update_firewall() + + def delete_firewall(self): + self._get_firewall() + if self.hcloud_firewall is not None: + if not self.module.check_mode: + self.client.firewalls.delete(self.hcloud_firewall) + self._mark_as_changed() + self.hcloud_firewall = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + rules=dict( + type="list", + elements="dict", + options=dict( + direction={"type": "str", "choices": ["in", "out"]}, + protocol={"type": "str", "choices": ["icmp", "udp", "tcp"]}, + port={"type": "str"}, + source_ips={"type": "list", "elements": "str"}, + destination_ips={"type": "list", "elements": "str"}, + ), + required_together=[["direction", "protocol", "source_ips"]] + ), + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + required_if=[['state', 'present', ['name']]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudFirewall.define_module() + + hcloud = AnsibleHcloudFirewall(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_firewall() + elif state == "present": + hcloud.present_firewall() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_server.py b/plugins/modules/hcloud_server.py index 849f273..88f8309 100644 --- a/plugins/modules/hcloud_server.py +++ b/plugins/modules/hcloud_server.py @@ -49,6 +49,11 @@ options: - List of Volumes IDs that should be attached to the server on server creation. type: list elements: str + firewalls: + description: + - List of Firewall IDs that should be attached to the server on server creation. + type: list + elements: str image: description: - Image the server should be created from. @@ -255,12 +260,15 @@ try: from hcloud.volumes.domain import Volume from hcloud.ssh_keys.domain import SSHKey from hcloud.servers.domain import Server + from hcloud.firewalls.domain import Firewall, FirewallResource from hcloud import APIException except ImportError: APIException = None Volume = None SSHKey = None Server = None + Firewall = None + FirewallResource = None class AnsibleHcloudServer(Hcloud): @@ -314,9 +322,10 @@ class AnsibleHcloudServer(Hcloud): "user_data": self.module.params.get("user_data"), "labels": self.module.params.get("labels"), } - if self.client.images.get_by_name(self.module.params.get("image")) is not None: + image = self.client.images.get_by_name(self.module.params.get("image")) + if image is not None: # When image name is not available look for id instead - params["image"] = self.client.images.get_by_name(self.module.params.get("image")) + params["image"] = image else: params["image"] = self.client.images.get_by_id(self.module.params.get("image")) @@ -330,6 +339,15 @@ class AnsibleHcloudServer(Hcloud): params["volumes"] = [ Volume(id=volume_id) for volume_id in self.module.params.get("volumes") ] + if self.module.params.get("firewalls") is not None: + params["firewalls"] = [] + for fw in self.module.params.get("firewalls"): + f = self.client.firewalls.get_by_name(fw) + if f is not None: + # When firewall name is not available look for id instead + params["firewalls"].append(f) + else: + params["firewalls"].append(self.client.firewalls.get_by_id(fw)) if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: # When not given, the API will choose the location. @@ -399,6 +417,34 @@ class AnsibleHcloudServer(Hcloud): self.hcloud_server.update(labels=labels) self._mark_as_changed() + firewalls = self.module.params.get("firewalls") + if firewalls is not None and firewalls != self.hcloud_server.public_net.firewalls: + if not self.module.check_mode: + for f in self.hcloud_server.public_net.firewalls: + found = False + for fname in firewalls: + if f.firewall.name == fname: + found = True + if not found: + r = FirewallResource(type="server", server=self.hcloud_server) + actions = self.client.firewalls.remove_from_resources(f.firewall, [r]) + for a in actions: + a.wait_until_finished() + + for fname in firewalls: + found = False + fw = None + for f in self.hcloud_server.public_net.firewalls: + if f.firewall.name == fname: + found = True + fw = f + if not found and fw is not None: + r = FirewallResource(type="server", server=self.hcloud_server) + actions = self.client.firewalls.apply_to_resources(fw, [r]) + for a in actions: + a.wait_until_finished() + 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 @@ -517,6 +563,7 @@ class AnsibleHcloudServer(Hcloud): user_data={"type": "str"}, ssh_keys={"type": "list", "elements": "str"}, volumes={"type": "list", "elements": "str"}, + firewalls={"type": "list", "elements": "str"}, labels={"type": "dict"}, backups={"type": "bool", "default": False}, upgrade_disk={"type": "bool", "default": False}, diff --git a/tests/integration/targets/hcloud_firewall/aliases b/tests/integration/targets/hcloud_firewall/aliases new file mode 100644 index 0000000..55ec821 --- /dev/null +++ b/tests/integration/targets/hcloud_firewall/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/tests/integration/targets/hcloud_firewall/defaults/main.yml b/tests/integration/targets/hcloud_firewall/defaults/main.yml new file mode 100644 index 0000000..e7eff02 --- /dev/null +++ b/tests/integration/targets/hcloud_firewall/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_firewall_name: "{{hcloud_prefix}}-integration" diff --git a/tests/integration/targets/hcloud_firewall/meta/main.yml b/tests/integration/targets/hcloud_firewall/meta/main.yml new file mode 100644 index 0000000..407c901 --- /dev/null +++ b/tests/integration/targets/hcloud_firewall/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_firewall/tasks/main.yml b/tests/integration/targets/hcloud_firewall/tasks/main.yml new file mode 100644 index 0000000..684ca0d --- /dev/null +++ b/tests/integration/targets/hcloud_firewall/tasks/main.yml @@ -0,0 +1,169 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test missing required parameters on create firewall + hcloud_firewall: + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create firewall + assert: + that: + - result is failed + - 'result.msg == "one of the following is required: id, name"' + +- name: test create firewall with check mode + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + register: result + check_mode: yes +- name: test create firewall with check mode + assert: + that: + - result is changed + +- name: test create firewall + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + labels: + key: value + my-label: label + register: firewall +- name: verify create firewall + assert: + that: + - firewall is changed + - firewall.hcloud_firewall.name == "{{ hcloud_firewall_name }}" + - firewall.hcloud_firewall.rules | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('direction','equalto','in') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','icmp') | list | count == 1 + +- name: test create firewall idempotence + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + labels: + key: value + my-label: label + register: result +- name: verify create firewall idempotence + assert: + that: + - result is not changed + +- name: test update firewall rules + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + - direction: in + protocol: tcp + port: 80 + source_ips: + - 0.0.0.0/0 + - ::/0 + labels: + key: value + my-label: label + register: firewall +- name: verify update firewall rules + assert: + that: + - firewall is changed + - firewall.hcloud_firewall.name == "{{ hcloud_firewall_name }}" + - firewall.hcloud_firewall.rules | list | count == 2 + - firewall.hcloud_firewall.rules | selectattr('direction','equalto','in') | list | count == 2 + - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','icmp') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','tcp') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('port','equalto','80') | list | count == 1 + +- name: test update firewall rules idempotence + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + labels: + key: value + my-label: label + register: result +- name: verify update firewall rules idempotence + assert: + that: + - result is not changed + + +- name: test update firewall with check mode + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + register: result + check_mode: yes +- name: test create firewall with check mode + assert: + that: + - result is changed + +- name: test update firewall + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + labels: + key: value + register: result +- name: test update firewall + assert: + that: + - result is changed + - result.hcloud_firewall.name == "changed-{{ hcloud_firewall_name }}" + +- name: test update firewall with same labels + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + labels: + key: value + register: result +- name: test update firewall with same labels + assert: + that: + - result is not changed + +- name: test update firewall with other labels + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + labels: + key: value + test: "val123" + register: result +- name: test update firewall with other labels + assert: + that: + - result is changed + +- name: test rename firewall + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "{{ hcloud_firewall_name }}" + register: result +- name: test rename firewall + assert: + that: + - result is changed + - result.hcloud_firewall.name == "{{ hcloud_firewall_name }}" + +- name: absent firewall + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success diff --git a/tests/integration/targets/hcloud_server/defaults/main.yml b/tests/integration/targets/hcloud_server/defaults/main.yml index 96b5287..bebb589 100644 --- a/tests/integration/targets/hcloud_server/defaults/main.yml +++ b/tests/integration/targets/hcloud_server/defaults/main.yml @@ -3,3 +3,4 @@ --- hcloud_prefix: "tests" hcloud_server_name: "{{hcloud_prefix}}-i" +hcloud_firewall_name: "{{hcloud_prefix}}-i" diff --git a/tests/integration/targets/hcloud_server/tasks/main.yml b/tests/integration/targets/hcloud_server/tasks/main.yml index f4464f1..18132f1 100644 --- a/tests/integration/targets/hcloud_server/tasks/main.yml +++ b/tests/integration/targets/hcloud_server/tasks/main.yml @@ -25,7 +25,7 @@ hcloud_server: name: "{{ hcloud_server_name }}" server_type: cx11 - image: ubuntu-18.04 + image: ubuntu-20.04 state: present register: result check_mode: yes @@ -38,7 +38,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: ubuntu-18.04 + image: ubuntu-20.04 state: started register: main_server - name: verify create server @@ -239,7 +239,7 @@ - name: test rebuild server hcloud_server: name: "{{ hcloud_server_name }}" - image: ubuntu-18.04 + image: ubuntu-20.04 state: rebuild register: result_after_test - name: verify rebuild server @@ -251,7 +251,7 @@ - name: test rebuild server with check mode hcloud_server: name: "{{ hcloud_server_name }}" - image: ubuntu-18.04 + image: ubuntu-20.04 state: rebuild register: result_after_test check_mode: true @@ -327,7 +327,7 @@ - name: test rebuild server fails if it is protected hcloud_server: name: "{{hcloud_server_name}}" - image: ubuntu-18.04 + image: ubuntu-20.04 state: rebuild ignore_errors: yes register: result @@ -366,7 +366,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: "ubuntu-18.04" + image: "ubuntu-20.04" ssh_keys: - ci@ansible.hetzner.cloud state: started @@ -394,7 +394,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: "ubuntu-18.04" + image: "ubuntu-20.04" ssh_keys: - ci@ansible.hetzner.cloud rescue_mode: "linux64" @@ -424,7 +424,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: ubuntu-18.04 + image: ubuntu-20.04 state: started register: main_server - name: verify setup server @@ -503,7 +503,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: "ubuntu-18.04" + image: "ubuntu-20.04" ssh_keys: - ci@ansible.hetzner.cloud labels: @@ -522,7 +522,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: "ubuntu-18.04" + image: "ubuntu-20.04" ssh_keys: - ci@ansible.hetzner.cloud labels: @@ -541,7 +541,7 @@ hcloud_server: name: "{{ hcloud_server_name}}" server_type: cx11 - image: "ubuntu-18.04" + image: "ubuntu-20.04" ssh_keys: - ci@ansible.hetzner.cloud labels: @@ -569,7 +569,7 @@ name: "{{ hcloud_server_name }}" server_type: cpx11 backups: true - image: "ubuntu-18.04" + image: "ubuntu-20.04" ssh_keys: - ci@ansible.hetzner.cloud state: present @@ -580,7 +580,7 @@ - result is changed - result.hcloud_server.backup_window != "" -- name: test create server with enabled backups +- name: cleanup test create server with enabled backups hcloud_server: name: "{{ hcloud_server_name }}" state: absent @@ -646,3 +646,85 @@ assert: that: - result is success + +- name: test create firewall + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + register: firewall +- name: verify create firewall + assert: + that: + - firewall is changed + +- name: test create firewall + hcloud_firewall: + name: "{{ hcloud_firewall_name }}2" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + register: firewall2 +- name: verify create firewall + assert: + that: + - firewall2 is changed + +- name: test create server with firewalls + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + firewalls: + - "{{ hcloud_firewall_name }}" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify enable backups + assert: + that: + - result is changed + +- name: test update server with firewalls + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + firewalls: + - "{{ hcloud_firewall_name }}2" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify enable backups + assert: + that: + - result is changed + +- name: cleanup test create firewall + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + state: absent + register: result +- name: verify create firewall + assert: + that: + - result is changed + +- name: cleanup test create server with firewalls + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/tests/utils/gitlab/sanity.sh b/tests/utils/gitlab/sanity.sh index e7f708f..4ee96ae 100755 --- a/tests/utils/gitlab/sanity.sh +++ b/tests/utils/gitlab/sanity.sh @@ -39,7 +39,7 @@ fi pip install pycodestyle pip install yamllint pip install voluptuous -pip install pylint +pip install pylint==2.5.3 # shellcheck disable=SC2086 ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ --base-branch "${base_branch}" \