From 2757fe745fcd80409290a453db72e9e6e4016f8f Mon Sep 17 00:00:00 2001 From: Jonas L Date: Thu, 1 Feb 2024 16:50:13 +0100 Subject: [PATCH] feat: improve firewall resources management (#324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ##### SUMMARY - firewall - Return resources the firewall is `applied_to`. - firewall_info - Add new `firewall_info` module to gather firewalls info. - firewall_resource - Add new `firewall_resource` module to manage firewalls resources. Fixes #111 ##### ISSUE TYPE - Feature Pull Request ##### COMPONENT NAME firewall firewall_info firewall_resource --------- Co-authored-by: Julian Tölle --- .../improve-firewall-resources-management.yml | 4 + examples/server-with-firewall.yml | 62 +++++ meta/runtime.yml | 6 + plugins/modules/firewall.py | 66 ++++- plugins/modules/firewall_info.py | 246 ++++++++++++++++++ plugins/modules/firewall_resource.py | 243 +++++++++++++++++ .../targets/firewall/tasks/test.yml | 1 + .../integration/targets/firewall_info/aliases | 2 + .../firewall_info/defaults/main/common.yml | 12 + .../firewall_info/defaults/main/main.yml | 5 + .../targets/firewall_info/tasks/cleanup.yml | 10 + .../targets/firewall_info/tasks/main.yml | 31 +++ .../targets/firewall_info/tasks/prepare.yml | 35 +++ .../targets/firewall_info/tasks/test.yml | 93 +++++++ .../targets/firewall_resource/aliases | 2 + .../defaults/main/common.yml | 12 + .../firewall_resource/defaults/main/main.yml | 5 + .../firewall_resource/tasks/cleanup.yml | 10 + .../targets/firewall_resource/tasks/main.yml | 31 +++ .../firewall_resource/tasks/prepare.yml | 25 ++ .../targets/firewall_resource/tasks/test.yml | 95 +++++++ tests/sanity/ignore-2.13.txt | 4 + tests/sanity/ignore-2.14.txt | 4 + 23 files changed, 1001 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/improve-firewall-resources-management.yml create mode 100644 examples/server-with-firewall.yml create mode 100644 plugins/modules/firewall_info.py create mode 100644 plugins/modules/firewall_resource.py create mode 100644 tests/integration/targets/firewall_info/aliases create mode 100644 tests/integration/targets/firewall_info/defaults/main/common.yml create mode 100644 tests/integration/targets/firewall_info/defaults/main/main.yml create mode 100644 tests/integration/targets/firewall_info/tasks/cleanup.yml create mode 100644 tests/integration/targets/firewall_info/tasks/main.yml create mode 100644 tests/integration/targets/firewall_info/tasks/prepare.yml create mode 100644 tests/integration/targets/firewall_info/tasks/test.yml create mode 100644 tests/integration/targets/firewall_resource/aliases create mode 100644 tests/integration/targets/firewall_resource/defaults/main/common.yml create mode 100644 tests/integration/targets/firewall_resource/defaults/main/main.yml create mode 100644 tests/integration/targets/firewall_resource/tasks/cleanup.yml create mode 100644 tests/integration/targets/firewall_resource/tasks/main.yml create mode 100644 tests/integration/targets/firewall_resource/tasks/prepare.yml create mode 100644 tests/integration/targets/firewall_resource/tasks/test.yml diff --git a/changelogs/fragments/improve-firewall-resources-management.yml b/changelogs/fragments/improve-firewall-resources-management.yml new file mode 100644 index 0000000..ba3c4bc --- /dev/null +++ b/changelogs/fragments/improve-firewall-resources-management.yml @@ -0,0 +1,4 @@ +minor_changes: + - firewall - Return resources the firewall is `applied_to`. + - firewall_info - Add new `firewall_info` module to gather firewalls info. + - firewall_resource - Add new `firewall_resource` module to manage firewalls resources. diff --git a/examples/server-with-firewall.yml b/examples/server-with-firewall.yml new file mode 100644 index 0000000..0e57096 --- /dev/null +++ b/examples/server-with-firewall.yml @@ -0,0 +1,62 @@ +--- +- name: Demonstrate creating servers with a firewall + hosts: localhost + connection: local + + vars: + servers: + - name: my-server1 + - name: my-server2 + + tasks: + - name: Create firewall + hetzner.hcloud.firewall: + name: my-firewall + rules: + - description: allow icmp from everywhere + direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + - description: allow ssh from everywhere + direction: in + protocol: tcp + port: 22 + source_ips: + - 0.0.0.0/0 + - ::/0 + state: present + + - name: Create servers + hetzner.hcloud.server: + name: "{{ item.name }}" + server_type: cx11 + image: debian-12 + labels: + kind: runners + state: started + loop: "{{ servers }}" + + - name: Apply firewall to resources using label selectors + hetzner.hcloud.firewall_resource: + firewall: my-firewall + label_selectors: [kind=runners] + state: present + + - name: Apply firewall to individual servers + hetzner.hcloud.firewall_resource: + firewall: my-firewall + servers: "{{ servers | map(attribute='name') }}" + state: present + + - name: Delete firewall + hetzner.hcloud.firewall: + name: my-firewall + state: absent + + - name: Delete servers + hetzner.hcloud.server: + name: "{{ item.name }}" + state: absent + loop: "{{ servers }}" diff --git a/meta/runtime.yml b/meta/runtime.yml index fc99a12..9891ce7 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -6,6 +6,8 @@ action_groups: - certificate_info - datacenter_info - firewall + - firewall_info + - firewall_resource - floating_ip - floating_ip_info - image_info @@ -44,6 +46,10 @@ plugin_routing: redirect: hetzner.hcloud.datacenter_info hcloud_firewall: redirect: hetzner.hcloud.firewall + hcloud_firewall_info: + redirect: hetzner.hcloud.firewall_info + hcloud_firewall_resource: + redirect: hetzner.hcloud.firewall_resource hcloud_floating_ip_info: redirect: hetzner.hcloud.floating_ip_info hcloud_floating_ip: diff --git a/plugins/modules/firewall.py b/plugins/modules/firewall.py index 6a8250a..8f6d0fb 100644 --- a/plugins/modules/firewall.py +++ b/plugins/modules/firewall.py @@ -175,6 +175,40 @@ hcloud_firewall: elements: str returned: always sample: [] + applied_to: + description: List of Resources the Firewall is applied to. + returned: always + type: list + elements: dict + contains: + type: + description: Type of the resource. + type: str + choices: [server, label_selector] + sample: label_selector + server: + description: ID of the server. + type: int + sample: 12345 + label_selector: + description: Label selector value. + type: str + sample: env=prod + applied_to_resources: + description: List of Resources the Firewall label selector is applied to. + returned: if RV(hcloud_firewall.applied_to[].type=label_selector) + type: list + elements: dict + contains: + type: + description: Type of resource referenced. + type: str + choices: [server] + sample: server + server: + description: ID of the Server. + type: int + sample: 12345 """ import time @@ -184,7 +218,11 @@ from ansible.module_utils.common.text.converters import to_native from ..module_utils.hcloud import AnsibleHCloud from ..module_utils.vendor.hcloud import APIException, HCloudException -from ..module_utils.vendor.hcloud.firewalls import BoundFirewall, FirewallRule +from ..module_utils.vendor.hcloud.firewalls import ( + BoundFirewall, + FirewallResource, + FirewallRule, +) class AnsibleHCloudFirewall(AnsibleHCloud): @@ -198,11 +236,12 @@ class AnsibleHCloudFirewall(AnsibleHCloud): "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, + "applied_to": [self._prepare_result_applied_to(resource) for resource in self.hcloud_firewall.applied_to], } - def _prepare_result_rule(self, rule): + def _prepare_result_rule(self, rule: FirewallRule): return { - "direction": rule.direction, + "direction": to_native(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], @@ -210,6 +249,24 @@ class AnsibleHCloudFirewall(AnsibleHCloud): "description": to_native(rule.description) if rule.description is not None else None, } + def _prepare_result_applied_to(self, resource: FirewallResource): + result = { + "type": to_native(resource.type), + "server": to_native(resource.server.id) if resource.server is not None else None, + "label_selector": ( + to_native(resource.label_selector.selector) if resource.label_selector is not None else None + ), + } + if resource.applied_to_resources is not None: + result["applied_to_resources"] = [ + { + "type": to_native(item.type), + "server": to_native(item.server.id) if item.server is not None else None, + } + for item in resource.applied_to_resources + ] + return result + def _get_firewall(self): try: if self.module.params.get("id") is not None: @@ -239,11 +296,13 @@ class AnsibleHCloudFirewall(AnsibleHCloud): ) for rule in rules ] + if not self.module.check_mode: try: self.client.firewalls.create(**params) except HCloudException as exception: self.fail_json_hcloud(exception, params=params) + self._mark_as_changed() self._get_firewall() @@ -277,6 +336,7 @@ class AnsibleHCloudFirewall(AnsibleHCloud): ] self.hcloud_firewall.set_rules(new_rules) self._mark_as_changed() + self._get_firewall() def present_firewall(self): diff --git a/plugins/modules/firewall_info.py b/plugins/modules/firewall_info.py new file mode 100644 index 0000000..7e7a623 --- /dev/null +++ b/plugins/modules/firewall_info.py @@ -0,0 +1,246 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: firewall_info +short_description: Gather infos about the Hetzner Cloud Firewalls. + +description: + - Gather facts about your Hetzner Cloud Firewalls. + +author: + - Jonas Lammler (@jooola) + +options: + id: + description: + - The ID of the Firewall you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name for the Firewall you want to get. + type: str + label_selector: + description: + - The label selector for the Firewalls you want to get. + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather hcloud Firewall infos + hetzner.hcloud.firewall_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_firewall_info: + description: List of Firewalls. + returned: always + type: list + elements: dict + 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 + labels: + description: User-defined labels (key-value pairs). + returned: always + type: dict + rules: + description: List of rules the firewall contain. + returned: always + type: list + elements: dict + contains: + description: + description: User defined description of this rule. + type: str + returned: always + sample: allow http from anywhere + direction: + description: The direction of the firewall rule. + type: str + returned: always + sample: in + protocol: + description: The protocol of the firewall rule. + type: str + returned: always + sample: tcp + port: + description: The port or port range allowed by this rule. + type: str + returned: if RV(hcloud_firewall_info[].rules[].protocol=tcp) or RV(hcloud_firewall_info[].rules[].protocol=udp) + sample: "80" + source_ips: + description: List of source CIDRs that are allowed within this rule. + type: list + elements: str + returned: always + sample: ["0.0.0.0/0", "::/0"] + destination_ips: + description: List of destination CIDRs that are allowed within this rule. + type: list + elements: str + returned: always + sample: [] + applied_to: + description: List of Resources the Firewall is applied to. + returned: always + type: list + elements: dict + contains: + type: + description: Type of the resource. + type: str + choices: [server, label_selector] + sample: label_selector + server: + description: ID of the server. + type: int + sample: 12345 + label_selector: + description: Label selector value. + type: str + sample: env=prod + applied_to_resources: + description: List of Resources the Firewall label selector is applied to. + returned: if RV(hcloud_firewall_info[].applied_to[].type=label_selector) + type: list + elements: dict + contains: + type: + description: Type of resource referenced. + type: str + choices: [server] + sample: server + server: + description: ID of the Server. + type: int + sample: 12345 +""" + +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.firewalls import ( + BoundFirewall, + FirewallResource, + FirewallRule, +) + + +class AnsibleHCloudFirewallInfo(AnsibleHCloud): + represent = "hcloud_firewall_info" + + hcloud_firewall_info: list[BoundFirewall] | None = None + + def _prepare_result(self): + tmp = [] + + for firewall in self.hcloud_firewall_info: + if firewall is None: + continue + + tmp.append( + { + "id": to_native(firewall.id), + "name": to_native(firewall.name), + "labels": firewall.labels, + "rules": [self._prepare_result_rule(rule) for rule in firewall.rules], + "applied_to": [self._prepare_result_applied_to(resource) for resource in firewall.applied_to], + } + ) + + return tmp + + def _prepare_result_rule(self, rule: FirewallRule): + return { + "description": to_native(rule.description) if rule.description is not None else None, + "direction": to_native(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 _prepare_result_applied_to(self, resource: FirewallResource): + result = { + "type": to_native(resource.type), + "server": to_native(resource.server.id) if resource.server is not None else None, + "label_selector": ( + to_native(resource.label_selector.selector) if resource.label_selector is not None else None + ), + } + if resource.applied_to_resources is not None: + result["applied_to_resources"] = [ + { + "type": to_native(item.type), + "server": to_native(item.server.id) if item.server is not None else None, + } + for item in resource.applied_to_resources + ] + return result + + def get_firewalls(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_firewall_info = [self.client.firewalls.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_firewall_info = [self.client.firewalls.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_firewall_info = self.client.firewalls.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_firewall_info = self.client.firewalls.get_all() + + 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"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFirewallInfo.define_module() + hcloud = AnsibleHCloudFirewallInfo(module) + + hcloud.get_firewalls() + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/firewall_resource.py b/plugins/modules/firewall_resource.py new file mode 100644 index 0000000..207f270 --- /dev/null +++ b/plugins/modules/firewall_resource.py @@ -0,0 +1,243 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: firewall_resource +short_description: Manage Resources a Hetzner Cloud Firewall is applied to. + +description: + - Add and Remove Resources a Hetzner Cloud Firewall is applied to. + +author: + - Jonas Lammler (@jooola) + +version_added: 2.5.0 +options: + firewall: + description: + - Name or ID of the Hetzner Cloud Firewall. + type: str + required: true + servers: + description: + - List of Server Name or ID. + type: list + elements: str + label_selectors: + description: + - List of Label Selector. + type: list + elements: str + state: + description: + - State of the firewall resources. + default: present + choices: [absent, present] + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Apply a firewall to a list of servers + hetzner.hcloud.firewall_resource: + name: my-firewall + servers: + - my-server + - 3456789 + state: present + +- name: Remove a firewall from a list of servers + hetzner.hcloud.firewall_resource: + name: my-firewall + servers: + - my-server + - 3456789 + state: absent + +- name: Apply a firewall to resources using label selectors + hetzner.hcloud.firewall_resource: + name: my-firewall + label_selectors: + - env=prod + state: present + +- name: Remove a firewall from resources using label selectors + hetzner.hcloud.firewall_resource: + name: my-firewall + label_selectors: + - env=prod + state: absent +""" + +RETURN = """ +hcloud_firewall_resource: + description: The Resources a Hetzner Cloud Firewall is applied to. + returned: always + type: dict + contains: + firewall: + description: + - Name of the Hetzner Cloud Firewall. + type: str + sample: my-firewall + servers: + description: + - List of Server Name. + type: list + elements: str + sample: [my-server1, my-server2] + label_selectors: + description: + - List of Label Selector. + type: list + elements: str + sample: [env=prod] +""" + +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.firewalls import ( + BoundFirewall, + FirewallResource, + FirewallResourceLabelSelector, +) +from ..module_utils.vendor.hcloud.servers import BoundServer + + +class AnsibleHCloudFirewallResource(AnsibleHCloud): + represent = "hcloud_firewall_resource" + + hcloud_firewall_resource: BoundFirewall | None = None + + def _prepare_result(self): + servers = [] + label_selectors = [] + for resource in self.hcloud_firewall_resource.applied_to: + if resource.type == FirewallResource.TYPE_SERVER: + servers.append(to_native(resource.server.name)) + elif resource.type == FirewallResource.TYPE_LABEL_SELECTOR: + label_selectors.append(to_native(resource.label_selector.selector)) + + return { + "firewall": to_native(self.hcloud_firewall_resource.name), + "servers": servers, + "label_selectors": label_selectors, + } + + def _get_firewall(self): + try: + self.hcloud_firewall_resource = self._client_get_by_name_or_id( + "firewalls", + self.module.params.get("firewall"), + ) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _diff_firewall_resources(self, operator) -> list[FirewallResource]: + before = self._prepare_result() + + resources: list[FirewallResource] = [] + + servers: list[str] | None = self.module.params.get("servers") + if servers: + for server_param in servers: + try: + server: BoundServer = self._client_get_by_name_or_id("servers", server_param) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + if operator(server.name, before["servers"]): + resources.append( + FirewallResource( + type=FirewallResource.TYPE_SERVER, + server=server, + ) + ) + + label_selectors = self.module.params.get("label_selectors") + if label_selectors: + for label_selector in label_selectors: + if operator(label_selector, before["label_selectors"]): + resources.append( + FirewallResource( + type=FirewallResource.TYPE_LABEL_SELECTOR, + label_selector=FirewallResourceLabelSelector(selector=label_selector), + ) + ) + + return resources + + def present_firewall_resources(self): + self._get_firewall() + resources = self._diff_firewall_resources( + lambda to_add, before: to_add not in before, + ) + if resources: + if not self.module.check_mode: + actions = self.hcloud_firewall_resource.apply_to_resources(resources=resources) + for action in actions: + action.wait_until_finished() + + self.hcloud_firewall_resource.reload() + + self._mark_as_changed() + + def absent_firewall_resources(self): + self._get_firewall() + resources = self._diff_firewall_resources( + lambda to_remove, before: to_remove in before, + ) + if resources: + if not self.module.check_mode: + actions = self.hcloud_firewall_resource.remove_from_resources(resources=resources) + for action in actions: + action.wait_until_finished() + + self.hcloud_firewall_resource.reload() + + self._mark_as_changed() + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec={ + "firewall": {"type": "str", "required": True}, + "servers": {"type": "list", "elements": "str"}, + "label_selectors": {"type": "list", "elements": "str"}, + "state": { + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + }, + required_one_of=[["servers", "label_selectors"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFirewallResource.define_module() + + hcloud = AnsibleHCloudFirewallResource(module) + state = module.params.get("state") + if state == "absent": + hcloud.absent_firewall_resources() + elif state == "present": + hcloud.present_firewall_resources() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/firewall/tasks/test.yml b/tests/integration/targets/firewall/tasks/test.yml index 790ed56..ff58170 100644 --- a/tests/integration/targets/firewall/tasks/test.yml +++ b/tests/integration/targets/firewall/tasks/test.yml @@ -51,6 +51,7 @@ - result.hcloud_firewall.rules[0].protocol == "icmp" - result.hcloud_firewall.rules[0].source_ips == ["0.0.0.0/0", "::/0"] - result.hcloud_firewall.labels.key == "value" + - result.hcloud_firewall.applied_to | list | count == 0 - name: Test create idempotency hetzner.hcloud.firewall: diff --git a/tests/integration/targets/firewall_info/aliases b/tests/integration/targets/firewall_info/aliases new file mode 100644 index 0000000..55ec821 --- /dev/null +++ b/tests/integration/targets/firewall_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/tests/integration/targets/firewall_info/defaults/main/common.yml b/tests/integration/targets/firewall_info/defaults/main/common.yml new file mode 100644 index 0000000..e316b23 --- /dev/null +++ b/tests/integration/targets/firewall_info/defaults/main/common.yml @@ -0,0 +1,12 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('first') | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" diff --git a/tests/integration/targets/firewall_info/defaults/main/main.yml b/tests/integration/targets/firewall_info/defaults/main/main.yml new file mode 100644 index 0000000..441e948 --- /dev/null +++ b/tests/integration/targets/firewall_info/defaults/main/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_server_name: "{{ hcloud_ns }}" +hcloud_firewall_name: "{{ hcloud_ns }}" diff --git a/tests/integration/targets/firewall_info/tasks/cleanup.yml b/tests/integration/targets/firewall_info/tasks/cleanup.yml new file mode 100644 index 0000000..37fbd34 --- /dev/null +++ b/tests/integration/targets/firewall_info/tasks/cleanup.yml @@ -0,0 +1,10 @@ +--- +- name: Cleanup test_server + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: Cleanup test_firewall + hetzner.hcloud.firewall: + name: "{{ hcloud_firewall_name }}" + state: absent diff --git a/tests/integration/targets/firewall_info/tasks/main.yml b/tests/integration/targets/firewall_info/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/firewall_info/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/firewall_info/tasks/prepare.yml b/tests/integration/targets/firewall_info/tasks/prepare.yml new file mode 100644 index 0000000..17d4ebc --- /dev/null +++ b/tests/integration/targets/firewall_info/tasks/prepare.yml @@ -0,0 +1,35 @@ +--- +- name: Create test_server + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: ubuntu-22.04 + labels: + firewall: "{{ hcloud_firewall_name }}" + state: stopped + register: test_server + +- name: Create test_firewall + hetzner.hcloud.firewall: + name: "{{ hcloud_firewall_name }}" + labels: + key: value + rules: + - description: allow icmp from anywhere + direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + state: present + register: test_firewall + +- name: Create test_firewall_resource + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + servers: + - "{{ hcloud_server_name }}" + label_selectors: + - firewall={{ hcloud_firewall_name }} + state: present + register: test_firewall_resource diff --git a/tests/integration/targets/firewall_info/tasks/test.yml b/tests/integration/targets/firewall_info/tasks/test.yml new file mode 100644 index 0000000..fc9a38a --- /dev/null +++ b/tests/integration/targets/firewall_info/tasks/test.yml @@ -0,0 +1,93 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Gather hcloud_firewall_info + hetzner.hcloud.firewall_info: + register: result +- name: Verify hcloud_firewall_info + ansible.builtin.assert: + that: + - result.hcloud_firewall_info | list | count >= 1 + +- name: Gather hcloud_firewall_info in check mode + hetzner.hcloud.firewall_info: + check_mode: true + register: result +- name: Verify hcloud_firewall_info in check mode + ansible.builtin.assert: + that: + - result.hcloud_firewall_info | list | count >= 1 + +- name: Gather hcloud_firewall_info with correct id + hetzner.hcloud.firewall_info: + id: "{{ test_firewall.hcloud_firewall.id }}" + register: result +- name: Verify hcloud_firewall_info with correct id + ansible.builtin.assert: + that: + - result.hcloud_firewall_info | list | count == 1 + - result.hcloud_firewall_info[0].name == hcloud_firewall_name + - result.hcloud_firewall_info[0].labels.key == "value" + - result.hcloud_firewall_info[0].rules | list | count == 1 + - result.hcloud_firewall_info[0].rules[0].description == "allow icmp from anywhere" + - result.hcloud_firewall_info[0].rules[0].direction == "in" + - result.hcloud_firewall_info[0].rules[0].protocol == "icmp" + - result.hcloud_firewall_info[0].rules[0].source_ips == ["0.0.0.0/0", "::/0"] + - result.hcloud_firewall_info[0].applied_to | list | count == 2 + - > + result.hcloud_firewall_info[0].applied_to + | selectattr('type', 'equalto', 'label_selector') + | list | count == 1 + - > + result.hcloud_firewall_info[0].applied_to + | selectattr('type', 'equalto', 'server') + | list | count == 1 + +- name: Gather hcloud_firewall_info with wrong id + hetzner.hcloud.firewall_info: + id: "{{ test_firewall.hcloud_firewall.id }}4321" + ignore_errors: true + register: result +- name: Verify hcloud_firewall_info with wrong id + ansible.builtin.assert: + that: + - result is failed + +- name: Gather hcloud_firewall_info with correct name + hetzner.hcloud.firewall_info: + name: "{{ hcloud_firewall_name }}" + register: result +- name: Verify hcloud_firewall_info with correct name + ansible.builtin.assert: + that: + - result.hcloud_firewall_info | list | count == 1 + +- name: Gather hcloud_firewall_info with wrong name + hetzner.hcloud.firewall_info: + name: "{{ hcloud_firewall_name }}-invalid" + register: result +- name: Verify hcloud_firewall_info with wrong name + ansible.builtin.assert: + that: + - result.hcloud_firewall_info | list | count == 0 + +- name: Gather hcloud_firewall_info with correct label selector + hetzner.hcloud.firewall_info: + label_selector: "key=value" + register: result +- name: Verify hcloud_firewall_info with correct label selector + ansible.builtin.assert: + that: + - > + result.hcloud_firewall_info + | selectattr('name', 'equalto', hcloud_firewall_name) + | list | count == 1 + +- name: Gather hcloud_firewall_info with wrong label selector + hetzner.hcloud.firewall_info: + label_selector: "key!=value" + register: result +- name: Verify hcloud_firewall_info with wrong label selector + ansible.builtin.assert: + that: + - result.hcloud_firewall_info | list | count == 0 diff --git a/tests/integration/targets/firewall_resource/aliases b/tests/integration/targets/firewall_resource/aliases new file mode 100644 index 0000000..0e88760 --- /dev/null +++ b/tests/integration/targets/firewall_resource/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +azp/group2 diff --git a/tests/integration/targets/firewall_resource/defaults/main/common.yml b/tests/integration/targets/firewall_resource/defaults/main/common.yml new file mode 100644 index 0000000..e316b23 --- /dev/null +++ b/tests/integration/targets/firewall_resource/defaults/main/common.yml @@ -0,0 +1,12 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +# Azure Pipelines will configure this value to something similar to +# "azp-84824-1-hetzner-2-13-test-2-13-hcloud-3-9-1-default-i" +hcloud_prefix: "tests" + +# Used to namespace resources created by concurrent test pipelines/targets +hcloud_run_ns: "{{ hcloud_prefix | md5 }}" +hcloud_role_ns: "{{ role_name | split('_') | map('first') | join() }}" +hcloud_ns: "ansible-{{ hcloud_run_ns }}-{{ hcloud_role_ns }}" diff --git a/tests/integration/targets/firewall_resource/defaults/main/main.yml b/tests/integration/targets/firewall_resource/defaults/main/main.yml new file mode 100644 index 0000000..441e948 --- /dev/null +++ b/tests/integration/targets/firewall_resource/defaults/main/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_server_name: "{{ hcloud_ns }}" +hcloud_firewall_name: "{{ hcloud_ns }}" diff --git a/tests/integration/targets/firewall_resource/tasks/cleanup.yml b/tests/integration/targets/firewall_resource/tasks/cleanup.yml new file mode 100644 index 0000000..37fbd34 --- /dev/null +++ b/tests/integration/targets/firewall_resource/tasks/cleanup.yml @@ -0,0 +1,10 @@ +--- +- name: Cleanup test_server + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: Cleanup test_firewall + hetzner.hcloud.firewall: + name: "{{ hcloud_firewall_name }}" + state: absent diff --git a/tests/integration/targets/firewall_resource/tasks/main.yml b/tests/integration/targets/firewall_resource/tasks/main.yml new file mode 100644 index 0000000..767fc46 --- /dev/null +++ b/tests/integration/targets/firewall_resource/tasks/main.yml @@ -0,0 +1,31 @@ +# +# DO NOT EDIT THIS FILE! Please edit the files in tests/integration/common instead. +# +--- +- name: Check if cleanup.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/cleanup.yml" + register: cleanup_file + +- name: Check if prepare.yml exists + ansible.builtin.stat: + path: "{{ role_path }}/tasks/prepare.yml" + register: prepare_file + +- name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists + +- name: Include prepare tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/prepare.yml" + when: prepare_file.stat.exists + +- name: Run tests + block: + - name: Include test tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/test.yml" + + always: + - name: Include cleanup tasks + ansible.builtin.include_tasks: "{{ role_path }}/tasks/cleanup.yml" + when: cleanup_file.stat.exists diff --git a/tests/integration/targets/firewall_resource/tasks/prepare.yml b/tests/integration/targets/firewall_resource/tasks/prepare.yml new file mode 100644 index 0000000..6fb6fd2 --- /dev/null +++ b/tests/integration/targets/firewall_resource/tasks/prepare.yml @@ -0,0 +1,25 @@ +--- +- name: Create test_server + hetzner.hcloud.server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: ubuntu-22.04 + labels: + key: value + state: stopped + register: test_server + +- name: Create test_firewall + hetzner.hcloud.firewall: + name: "{{ hcloud_firewall_name }}" + labels: + key: value + rules: + - description: allow icmp from anywhere + direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + state: present + register: test_firewall diff --git a/tests/integration/targets/firewall_resource/tasks/test.yml b/tests/integration/targets/firewall_resource/tasks/test.yml new file mode 100644 index 0000000..088e784 --- /dev/null +++ b/tests/integration/targets/firewall_resource/tasks/test.yml @@ -0,0 +1,95 @@ +# 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 + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + state: present + ignore_errors: true + register: result +- name: Verify missing required parameters + ansible.builtin.assert: + that: + - result is failed + - 'result.msg == "one of the following is required: servers, label_selectors"' + +- name: Test create with check mode + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + servers: ["{{ hcloud_server_name }}"] + check_mode: true + register: result +- name: Verify create with check mode + ansible.builtin.assert: + that: + - result is changed + +- name: Test create + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + servers: ["{{ hcloud_server_name }}"] + register: result +- name: Verify create + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_firewall_resource.firewall == hcloud_firewall_name + - result.hcloud_firewall_resource.servers | list | count == 1 + - result.hcloud_firewall_resource.servers[0] == hcloud_server_name + - result.hcloud_firewall_resource.label_selectors | list | count == 0 + +- name: Test create idempotency + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + servers: ["{{ hcloud_server_name }}"] + register: result +- name: Verify create idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test update + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + label_selectors: + - key=value + register: result +- name: Verify update + ansible.builtin.assert: + that: + - result is changed + - result.hcloud_firewall_resource.label_selectors | list | count == 1 + - result.hcloud_firewall_resource.label_selectors[0] == "key=value" + +- name: Test update idempotency + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + label_selectors: + - key=value + register: result +- name: Verify update idempotency + ansible.builtin.assert: + that: + - result is not changed + +- name: Test delete servers + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + servers: ["{{ hcloud_server_name }}"] + state: absent + register: result +- name: Verify delete + ansible.builtin.assert: + that: + - result is changed + +- name: Test delete label_selectors + hetzner.hcloud.firewall_resource: + firewall: "{{ hcloud_firewall_name }}" + label_selectors: ["key=value"] + state: absent + register: result +- name: Verify delete + ansible.builtin.assert: + that: + - result is changed diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index 23dcfbc..185a458 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -7,6 +7,10 @@ plugins/modules/certificate.py validate-modules:illegal-future-imports plugins/modules/certificate.py validate-modules:import-before-documentation plugins/modules/datacenter_info.py validate-modules:illegal-future-imports plugins/modules/datacenter_info.py validate-modules:import-before-documentation +plugins/modules/firewall_info.py validate-modules:illegal-future-imports +plugins/modules/firewall_info.py validate-modules:import-before-documentation +plugins/modules/firewall_resource.py validate-modules:illegal-future-imports +plugins/modules/firewall_resource.py validate-modules:import-before-documentation plugins/modules/firewall.py validate-modules:illegal-future-imports plugins/modules/firewall.py validate-modules:import-before-documentation plugins/modules/floating_ip_info.py validate-modules:illegal-future-imports diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 95982f3..e0d8362 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -6,6 +6,10 @@ plugins/modules/certificate.py validate-modules:illegal-future-imports plugins/modules/certificate.py validate-modules:import-before-documentation plugins/modules/datacenter_info.py validate-modules:illegal-future-imports plugins/modules/datacenter_info.py validate-modules:import-before-documentation +plugins/modules/firewall_info.py validate-modules:illegal-future-imports +plugins/modules/firewall_info.py validate-modules:import-before-documentation +plugins/modules/firewall_resource.py validate-modules:illegal-future-imports +plugins/modules/firewall_resource.py validate-modules:import-before-documentation plugins/modules/firewall.py validate-modules:illegal-future-imports plugins/modules/firewall.py validate-modules:import-before-documentation plugins/modules/floating_ip_info.py validate-modules:illegal-future-imports