From d2b567024f7dc67e427303bd453eca5e212fb25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20K=C3=A4mmerling?= Date: Mon, 29 Jun 2020 08:59:48 +0200 Subject: [PATCH] Start of hcloud_load_balancer_service --- .../modules/hcloud_load_balancer_service.py | 268 ++++++++++++++++++ .../hcloud_load_balancer_service/aliases | 2 + .../defaults/main.yml | 5 + .../meta/main.yml | 3 + .../tasks/main.yml | 85 ++++++ .../tasks/main.yml | 14 +- 6 files changed, 370 insertions(+), 7 deletions(-) create mode 100644 plugins/modules/hcloud_load_balancer_service.py create mode 100644 tests/integration/targets/hcloud_load_balancer_service/aliases create mode 100644 tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml create mode 100644 tests/integration/targets/hcloud_load_balancer_service/meta/main.yml create mode 100644 tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml diff --git a/plugins/modules/hcloud_load_balancer_service.py b/plugins/modules/hcloud_load_balancer_service.py new file mode 100644 index 0000000..bf5dd49 --- /dev/null +++ b/plugins/modules/hcloud_load_balancer_service.py @@ -0,0 +1,268 @@ +#!/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_load_balancer_service + +short_description: Create and manage cloud Load Balancers on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Load Balancers on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + load_balancer: + description: + - The ID of the Hetzner Cloud Load Balancer to manage. + type: int + listen_port: + description: + - The Name of the Hetzner Cloud Load Balancer to manage. + type: str + load_balancer_type: + description: + - The Load Balancer Type of the Hetzner Cloud Load Balancer to manage. + - Required if Load Balancer does not exists. + type: str + location: + description: + - Location of Load Balancer. + - Required if no I(network_zone) is given and Load Balancer does not exists. + type: str + network_zone: + description: + - Network Zone of Load Balancer. + - Required of no I(location) is given and Load Balancer does not exists. + type: str + labels: + description: + - User-defined labels (key-value pairs). + type: dict + disable_public_interface: + description: + - Disables the public interface. + type: bool + default: False + delete_protection: + description: + - Protect the Load Balancer for deletion. + type: bool + state: + description: + - State of the Load Balancer. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +requirements: + - hcloud-python >= 1.8.0 +''' + +EXAMPLES = """ +- name: Create a basic Load Balancer + hcloud_load_balancer_service: + name: my-Load Balancer + load_balancer_type: lb11 + location: fsn1 + state: present + +- name: Ensure the Load Balancer is absent (remove if needed) + hcloud_load_balancer_service: + name: my-Load Balancer + state: absent + +""" + +RETURN = """ +hcloud_load_balancer_service: + description: The Load Balancer instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the Load Balancer + returned: always + type: int + sample: 1937415 + name: + description: Name of the Load Balancer + returned: always + type: str + sample: my-Load-Balancer + status: + description: Status of the Load Balancer + returned: always + type: str + sample: running + load_balancer_type: + description: Name of the Load Balancer type of the Load Balancer + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the Load Balancer + returned: always + type: str + sample: 116.203.104.109 + ipv6_address: + description: Public IPv6 address of the Load Balancer + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::1 + location: + description: Name of the location of the Load Balancer + returned: always + type: str + sample: fsn1 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if Load Balancer is protected for deletion + type: bool + returned: always + sample: false + disable_public_interface: + description: True if Load Balancer public interface is disabled + type: bool + returned: always + sample: false +""" + +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.load_balancers.domain import LoadBalancer, LoadBalancerService + from hcloud import APIException +except ImportError: + pass + + +class AnsibleHcloudLoadBalancerService(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_service") + self.hcloud_load_balancer = None + self.hcloud_load_balancer_service = None + + def _prepare_result(self): + return { + "load_balancer": to_native(self.hcloud_load_balancer.name), + "protocol": to_native(self.hcloud_load_balancer_service.protocol), + "listen_port": to_native(self.hcloud_load_balancer_service.listen_port), + "destination_port": to_native(self.hcloud_load_balancer_service.destination_port), + "proxyprotocol": self.hcloud_load_balancer_service.proxyprotocol, + } + + def _get_load_balancer(self): + try: + self.hcloud_load_balancer = self.client.load_balancers.get_by_name( + self.module.params.get("load_balancer") + ) + self._get_load_balancer_service() + except APIException as e: + self.module.fail_json(msg=e.message) + + def _create_load_balancer(self): + + self.module.fail_on_missing_params( + required_params=["protocol"] + ) + if self.module.params.get("protocol") == "tcp": + self.module.fail_on_missing_params( + required_params=["destination_port"] + ) + + params = { + "protocol": self.module.params.get("protocol"), + "proxyprotocol": self.module.params.get("proxyprotocl") + } + + if not self.module.check_mode: + resp = self.hcloud_load_balancer.add_service(LoadBalancerService(**params)) + resp.action.wait_until_finished(max_retries=1000) + + self._mark_as_changed() + self._get_load_balancer() + self._get_load_balancer_service() + + def _update_load_balancer(self): + try: + self._get_load_balancer() + except APIException as e: + self.module.fail_json(msg=e.message) + + def _get_load_balancer_service(self): + for service in self.hcloud_load_balancer.services: + if self.module.params.get("listen_port") == service.listen_port: + self.hcloud_load_balancer_service = service + + def present_load_balancer_service(self): + self._get_load_balancer() + if self.hcloud_load_balancer_service is None: + self._create_load_balancer() + else: + self._update_load_balancer() + + def delete_load_balancer_service(self): + try: + self._get_load_balancer() + if self.hcloud_load_balancer_service is not None: + if not self.module.check_mode: + self.hcloud_load_balancer.delete_service(self.hcloud_load_balancer_service) + self._mark_as_changed() + self.hcloud_load_balancer_service = None + except APIException as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + load_balancer={"type": "str", "required": True}, + listen_port={"type": "int", "required": True}, + destination_port={"type": "int"}, + protocol={ + "choices": ["http", "https", "tcp"], + }, + proxyprotocol={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerService.define_module() + + hcloud = AnsibleHcloudLoadBalancerService(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_load_balancer_service() + elif state == "present": + hcloud.present_load_balancer_service() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/hcloud_load_balancer_service/aliases b/tests/integration/targets/hcloud_load_balancer_service/aliases new file mode 100644 index 0000000..18dc30b --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_service/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml b/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml new file mode 100644 index 0000000..0876695 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_service/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_load_balancer_name: "{{hcloud_prefix}}-load_balancer-target" diff --git a/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml b/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml new file mode 100644 index 0000000..407c901 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml b/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml new file mode 100644 index 0000000..17b907f --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml @@ -0,0 +1,85 @@ +# 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: setup load_balancer + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + load_balancer_type: lb11 + state: present + location: "fsn1" + register: load_balancer +- name: verify setup load_balancer + assert: + that: + - load_balancer is success + +- name: test create load_balancer service with checkmode + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + state: present + register: result + check_mode: yes +- name: verify test create load_balancer service with checkmode + assert: + that: + - result is changed + +- name: test create load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + state: present + register: load_balancer_service +- name: verify create load_balancer service + assert: + that: + - load_balancer_service is changed + - load_balancer_service.hcloud_load_balancer_service.protocol == "http" + - load_balancer_service.hcloud_load_balancer_service.listen_port == 80 + - load_balancer_service.hcloud_load_balancer_service.destination_port == 80 + - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false + +- name: test create load_balancer service idempotency + hcloud_load_balancer_service: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: load_balancer_service +- name: verify create load_balancer service idempotency + assert: + that: + - load_balancer_service is not changed + +- name: test absent load_balancer service + hcloud_load_balancer_service: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify test absent load_balancer service + assert: + that: + - result is changed + +- name: cleanup load_balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify cleanup load_balancer + assert: + that: + - result is success + +- name: cleanup + hcloud_server: + name: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml b/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml index 87277e1..65fb9b0 100644 --- a/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml +++ b/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml @@ -45,14 +45,14 @@ load_balancer: "{{hcloud_load_balancer_name}}" server: "{{hcloud_server_name}}" state: present - register: load_balancerNetwork + register: load_balancer_target - name: verify create load_balancer target assert: that: - - load_balancerNetwork is changed - - load_balancerNetwork.hcloud_load_balancer_target.type == "server" - - load_balancerNetwork.hcloud_load_balancer_target.server == hcloud_server_name - - load_balancerNetwork.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name + - load_balancer_target is changed + - load_balancer_target.hcloud_load_balancer_target.type == "server" + - load_balancer_target.hcloud_load_balancer_target.server == hcloud_server_name + - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name - name: test create load_balancer target idempotency hcloud_load_balancer_target: @@ -60,11 +60,11 @@ load_balancer: "{{hcloud_load_balancer_name}}" server: "{{hcloud_server_name}}" state: present - register: load_balancerNetwork + register: load_balancer_target - name: verify create load_balancer target idempotency assert: that: - - load_balancerNetwork is not changed + - load_balancer_target is not changed - name: test absent load_balancer target hcloud_load_balancer_target: