diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a524eb2..f5681c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,7 @@ stages: ansible-devel-1/4: stage: sanity image: python:3.8-buster + allow_failure: true except: - tags script: @@ -15,6 +16,7 @@ ansible-devel-1/4: ansible-devel-2/4: stage: sanity image: python:3.8-buster + allow_failure: true except: - tags script: @@ -25,6 +27,7 @@ ansible-devel-2/4: ansible-devel-3/4: stage: sanity image: python:3.8-buster + allow_failure: true except: - tags script: @@ -35,6 +38,7 @@ ansible-devel-3/4: ansible-devel-4/4: stage: sanity image: python:3.8-buster + allow_failure: true except: - tags script: diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml new file mode 100644 index 0000000..16bd834 --- /dev/null +++ b/changelogs/changelog.yaml @@ -0,0 +1,25 @@ +ancestor: null +releases: + 0.1.0: + modules: + - description: Create and manage cloud Floating IPs on the Hetzner Cloud. + name: hcloud_floating_ip + namespace: '' + - description: Create and manage cloud Load Balancers on the Hetzner Cloud. + name: hcloud_load_balancer + namespace: '' + - description: Manage the relationship between Hetzner Cloud Networks and Load + Balancers + name: hcloud_load_balancer_network + namespace: '' + - description: Create and manage the services of cloud Load Balancers on the Hetzner + Cloud. + name: hcloud_load_balancer_service + namespace: '' + - description: Manage Hetzner Cloud Load Balancer targets + name: hcloud_load_balancer_target + namespace: '' + - description: Gather infos about the Hetzner Cloud Load Balancer types. + name: hcloud_load_balancer_type_info + namespace: '' + release_date: '2020-06-29' diff --git a/plugins/modules/hcloud_certificate.py b/plugins/modules/hcloud_certificate.py new file mode 100644 index 0000000..e6571b2 --- /dev/null +++ b/plugins/modules/hcloud_certificate.py @@ -0,0 +1,264 @@ +#!/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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = ''' +--- +module: hcloud_certificate + +short_description: Create and manage certificates on the Hetzner Cloud. + + +description: + - Create, update and manage certificates on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud certificate to manage. + - Only required if no certificate I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud certificate to manage. + - Only required if no certificate I(id) is given or a certificate does not exists. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + certificate: + description: + - Certificate and chain in PEM format, in order so that each record directly certifies the one preceding. + - Required if certificate does not exists. + type: str + private_key: + description: + - Certificate key in PEM format. + - Required if certificate does not exists. + type: str + state: + description: + - State of the certificate. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic certificate + hcloud_certificate: + name: my-certificate + certificate: "ssh-rsa AAAjjk76kgf...Xt" + private_key: "ssh-rsa AAAjjk76kgf...Xt" + state: present + +- name: Create a certificate with labels + hcloud_certificate: + name: my-certificate + certificate: "ssh-rsa AAAjjk76kgf...Xt" + private_key: "ssh-rsa AAAjjk76kgf...Xt" + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the certificate is absent (remove if needed) + hcloud_certificate: + name: my-certificate + state: absent +""" + +RETURN = """ +hcloud_certificate: + description: The certificate instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the certificate + returned: always + type: int + sample: 1937415 + name: + description: Name of the certificate + returned: always + type: str + sample: my website cert + fingerprint: + description: Fingerprint of the certificate + returned: always + type: str + sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f" + certificate: + description: Certificate and chain in PEM format + returned: always + type: str + sample: "-----BEGIN CERTIFICATE-----..." + domain_names: + description: List of Domains and Subdomains covered by the Certificate + returned: always + type: dict + not_valid_before: + description: Point in time when the Certificate becomes valid (in ISO-8601 format) + returned: always + type: str + not_valid_after: + description: Point in time when the Certificate stops being valid (in ISO-8601 format) + returned: always + type: str + 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.certificates.domain import Certificate + from hcloud.certificates.domain import Server + from hcloud import APIException +except ImportError: + pass + + +class AnsibleHcloudCertificate(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_certificate") + self.hcloud_certificate = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_certificate.id), + "name": to_native(self.hcloud_certificate.name), + "fingerprint": to_native(self.hcloud_certificate.fingerprint), + "certificate": to_native(self.hcloud_certificate.certificate), + "not_valid_before": to_native(self.hcloud_certificate.not_valid_before), + "not_valid_after": to_native(self.hcloud_certificate.not_valid_after), + "domain_names": [to_native(domain) for domain in self.hcloud_certificate.domain_names], + "labels": self.hcloud_certificate.labels + } + + def _get_certificate(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_certificate = self.client.certificates.get_by_id( + self.module.params.get("id") + ) + elif self.module.params.get("name") is not None: + self.hcloud_certificate = self.client.certificates.get_by_name( + self.module.params.get("name") + ) + + except APIException as e: + self.module.fail_json(msg=e.message) + + def _create_certificate(self): + self.module.fail_on_missing_params( + required_params=["name", "certificate", "private_key"] + ) + params = { + "name": self.module.params.get("name"), + "certificate": self.module.params.get("certificate"), + "private_key": self.module.params.get("private_key"), + "labels": self.module.params.get("labels") + } + + if not self.module.check_mode: + try: + self.client.certificates.create(**params) + except APIException as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_certificate() + + def _update_certificate(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_certificate.name != name: + self.module.fail_on_missing_params( + required_params=["id"] + ) + if not self.module.check_mode: + self.hcloud_certificate.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_certificate.labels != labels: + if not self.module.check_mode: + self.hcloud_certificate.update(labels=labels) + self._mark_as_changed() + + self._get_certificate() + + def present_certificate(self): + self._get_certificate() + if self.hcloud_certificate is None: + self._create_certificate() + else: + self._update_certificate() + + def delete_certificate(self): + self._get_certificate() + if self.hcloud_certificate is not None: + if not self.module.check_mode: + self.client.certificates.delete(self.hcloud_certificate) + self._mark_as_changed() + self.hcloud_certificate = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + certificate={"type": "str"}, + private_key={"type": "str"}, + 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 = AnsibleHcloudCertificate.define_module() + + hcloud = AnsibleHcloudCertificate(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_certificate() + elif state == "present": + hcloud.present_certificate() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_certificate_info.py b/plugins/modules/hcloud_certificate_info.py new file mode 100644 index 0000000..feb051a --- /dev/null +++ b/plugins/modules/hcloud_certificate_info.py @@ -0,0 +1,173 @@ +#!/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 + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = ''' +--- +module: hcloud_certificate_info +short_description: Gather infos about your Hetzner Cloud certificates. +description: + - Gather facts about your Hetzner Cloud certificates. +author: + - Lukas Kaemmerling (@LKaemmerling) +options: + id: + description: + - The ID of the certificate you want to get. + type: int + name: + description: + - The name of the certificate you want to get. + type: str + label_selector: + description: + - The label selector for the certificate you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud certificate infos + hcloud_certificate_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_certificate_info +""" + +RETURN = """ +hcloud_certificate_info: + description: The certificate instances + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the certificate + returned: always + type: int + sample: 1937415 + name: + description: Name of the certificate + returned: always + type: str + sample: my website cert + fingerprint: + description: Fingerprint of the certificate + returned: always + type: str + sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f" + certificate: + description: Certificate and chain in PEM format + returned: always + type: str + sample: "-----BEGIN CERTIFICATE-----..." + domain_names: + description: List of Domains and Subdomains covered by the Certificate + returned: always + type: dict + not_valid_before: + description: Point in time when the Certificate becomes valid (in ISO-8601 format) + returned: always + type: str + not_valid_after: + description: Point in time when the Certificate stops being valid (in ISO-8601 format) + returned: always + type: str + 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 import APIException +except ImportError: + pass + + +class AnsibleHcloudCertificateInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_certificate_info") + self.hcloud_certificate_info = None + + def _prepare_result(self): + certificates = [] + + for certificate in self.hcloud_certificate_info: + if certificate: + certificates.append({ + "id": to_native(certificate.id), + "name": to_native(certificate.name), + "fingerprint": to_native(certificate.fingerprint), + "certificate": to_native(certificate.certificate), + "not_valid_before": to_native(certificate.not_valid_before), + "not_valid_after": to_native(certificate.not_valid_after), + "domain_names": [to_native(domain) for domain in certificate.domain_names], + "labels": certificate.labels + }) + return certificates + + def get_certificates(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_certificate_info = [self.client.certificates.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_certificate_info = [self.client.certificates.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_certificate_info = self.client.certificates.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_certificate_info = self.client.certificates.get_all() + + except APIException as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudCertificateInfo.define_module() + + hcloud = AnsibleHcloudCertificateInfo(module) + hcloud.get_certificates() + result = hcloud.get_result() + + ansible_info = { + 'hcloud_certificate_info': result['hcloud_certificate_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_floating_ip.py b/plugins/modules/hcloud_floating_ip.py index e1c26ad..b66492f 100644 --- a/plugins/modules/hcloud_floating_ip.py +++ b/plugins/modules/hcloud_floating_ip.py @@ -20,7 +20,7 @@ description: author: - Lukas Kaemmerling (@lkaemmerling) - +version_added: 0.1.0 options: id: description: @@ -155,7 +155,7 @@ hcloud_floating_ip: type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" labels: description: User-defined labels (key-value pairs) type: dict diff --git a/plugins/modules/hcloud_floating_ip_info.py b/plugins/modules/hcloud_floating_ip_info.py index 003acf4..f3eb7da 100644 --- a/plugins/modules/hcloud_floating_ip_info.py +++ b/plugins/modules/hcloud_floating_ip_info.py @@ -61,7 +61,7 @@ hcloud_floating_ip_info: returned: Always type: str sample: my-floating-ip - version_added: "1.0.0" + version_added: "0.1.0" description: description: Description of the Floating IP returned: always @@ -91,7 +91,7 @@ hcloud_floating_ip_info: description: True if the Floating IP is protected for deletion returned: always type: bool - version_added: "1.0.0" + version_added: "0.1.0" labels: description: User-defined labels (key-value pairs) returned: always diff --git a/plugins/modules/hcloud_load_balancer.py b/plugins/modules/hcloud_load_balancer.py new file mode 100644 index 0000000..4454045 --- /dev/null +++ b/plugins/modules/hcloud_load_balancer.py @@ -0,0 +1,307 @@ +#!/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 + +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) +version_added: 0.1.0 +options: + id: + description: + - The ID of the Hetzner Cloud Load Balancer to manage. + - Only required if no Load Balancer I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud Load Balancer to manage. + - Only required if no Load Balancer I(id) is given or a Load Balancer does not exists. + 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: + 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: + name: my-Load Balancer + state: absent + +""" + +RETURN = """ +hcloud_load_balancer: + 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 + from hcloud import APIException +except ImportError: + pass + + +class AnsibleHcloudLoadBalancer(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer") + self.hcloud_load_balancer = None + + def _prepare_result(self): + private_ipv4_address = None if len(self.hcloud_load_balancer.private_net) == 0 else to_native( + self.hcloud_load_balancer.private_net[0].ip) + return { + "id": to_native(self.hcloud_load_balancer.id), + "name": to_native(self.hcloud_load_balancer.name), + "ipv4_address": to_native(self.hcloud_load_balancer.public_net.ipv4.ip), + "ipv6_address": to_native(self.hcloud_load_balancer.public_net.ipv6.ip), + "private_ipv4_address": private_ipv4_address, + "load_balancer_type": to_native(self.hcloud_load_balancer.load_balancer_type.name), + "location": to_native(self.hcloud_load_balancer.location.name), + "labels": self.hcloud_load_balancer.labels, + "delete_protection": self.hcloud_load_balancer.protection["delete"], + "disable_public_interface": self.hcloud_load_balancer.public_net.enabled + } + + def _get_load_balancer(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_load_balancer = self.client.load_balancers.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_load_balancer = self.client.load_balancers.get_by_name( + self.module.params.get("name") + ) + except APIException as e: + self.module.fail_json(msg=e.message) + + def _create_load_balancer(self): + + self.module.fail_on_missing_params( + required_params=["name", "load_balancer_type"] + ) + + params = { + "name": self.module.params.get("name"), + "load_balancer_type": self.client.load_balancer_types.get_by_name( + self.module.params.get("load_balancer_type") + ), + "labels": self.module.params.get("labels"), + } + + if self.module.params.get("location") is None and self.module.params.get("network_zone") is None: + self.module.fail_json(msg="one of the following is required: location, network_zone") + elif self.module.params.get("location") is not None and self.module.params.get("network_zone") is None: + params["location"] = self.client.locations.get_by_name( + self.module.params.get("location") + ) + elif self.module.params.get("location") is None and self.module.params.get("network_zone") is not None: + params["network_zone"] = self.module.params.get("network_zone") + + if not self.module.check_mode: + resp = self.client.load_balancers.create(**params) + resp.action.wait_until_finished(max_retries=1000) + + self._mark_as_changed() + self._get_load_balancer() + self._update_load_balancer() + + def _update_load_balancer(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_load_balancer.labels: + if not self.module.check_mode: + self.hcloud_load_balancer.update(labels=labels) + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_load_balancer.protection["delete"]: + if not self.module.check_mode: + self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + self._get_load_balancer() + + disable_public_interface = self.module.params.get("disable_public_interface") + if disable_public_interface is not None and disable_public_interface != self.hcloud_load_balancer.public_net.enabled: + if not self.module.check_mode: + if disable_public_interface is True: + self.hcloud_load_balancer.disable_public_interface().wait_until_finished() + else: + self.hcloud_load_balancer.enable_public_interface().wait_until_finished() + self._mark_as_changed() + self._get_load_balancer() + except APIException as e: + self.module.fail_json(msg=e.message) + + def present_load_balancer(self): + self._get_load_balancer() + if self.hcloud_load_balancer is None: + self._create_load_balancer() + else: + self._update_load_balancer() + + def delete_load_balancer(self): + try: + self._get_load_balancer() + if self.hcloud_load_balancer is not None: + if not self.module.check_mode: + self.client.load_balancers.delete(self.hcloud_load_balancer) + self._mark_as_changed() + self.hcloud_load_balancer = None + except APIException as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + load_balancer_type={"type": "str"}, + location={"type": "str"}, + network_zone={"type": "str"}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + disable_public_interface={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + mutually_exclusive=[["location", "network_zone"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancer.define_module() + + hcloud = AnsibleHcloudLoadBalancer(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_load_balancer() + elif state == "present": + hcloud.present_load_balancer() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_load_balancer_network.py b/plugins/modules/hcloud_load_balancer_network.py new file mode 100644 index 0000000..e0c9f2f --- /dev/null +++ b/plugins/modules/hcloud_load_balancer_network.py @@ -0,0 +1,201 @@ +#!/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_network + +short_description: Manage the relationship between Hetzner Cloud Networks and Load Balancers + + +description: + - Create and delete the relationship Hetzner Cloud Networks and Load Balancers + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 0.1.0 +options: + network: + description: + - The name of the Hetzner Cloud Networks. + type: str + required: true + load_balancer: + description: + - The name of the Hetzner Cloud Load Balancer. + type: str + required: true + ip: + description: + - The IP the Load Balancer should have. + type: str + state: + description: + - State of the load_balancer_network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.8.1 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic Load Balancer network + hcloud_load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + state: present + +- name: Create a Load Balancer network and specify the ip address + hcloud_load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + ip: 10.0.0.1 + state: present + +- name: Ensure the Load Balancer network is absent (remove if needed) + hcloud_load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + state: absent +""" + +RETURN = """ +hcloud_load_balancer_network: + description: The relationship between a Load Balancer and a network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-LoadBalancer + ip: + description: IP of the Load Balancer within the Network ip range + type: str + returned: always + sample: 10.0.0.8 +""" + +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 import APIException +except ImportError: + APIException = None + NetworkSubnet = None + + +class AnsibleHcloudLoadBalancerNetwork(Hcloud): + def __init__(self, module): + super(AnsibleHcloudLoadBalancerNetwork, self).__init__(module, "hcloud_load_balancer_network") + self.hcloud_network = None + self.hcloud_load_balancer = None + self.hcloud_load_balancer_network = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "load_balancer": to_native(self.hcloud_load_balancer.name), + "ip": to_native(self.hcloud_load_balancer_network.ip), + } + + def _get_load_balancer_and_network(self): + try: + self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) + self.hcloud_load_balancer = self.client.load_balancers.get_by_name(self.module.params.get("load_balancer")) + self.hcloud_load_balancer_network = None + except APIException as e: + self.module.fail_json(msg=e.message) + + def _get_load_balancer_network(self): + for privateNet in self.hcloud_load_balancer.private_net: + if privateNet.network.id == self.hcloud_network.id: + self.hcloud_load_balancer_network = privateNet + + def _create_load_balancer_network(self): + params = { + "network": self.hcloud_network + } + + if self.module.params.get("ip") is not None: + params["ip"] = self.module.params.get("ip") + + if not self.module.check_mode: + try: + self.hcloud_load_balancer.attach_to_network(**params).wait_until_finished() + except APIException as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_load_balancer_and_network() + self._get_load_balancer_network() + + def present_load_balancer_network(self): + self._get_load_balancer_and_network() + self._get_load_balancer_network() + if self.hcloud_load_balancer_network is None: + self._create_load_balancer_network() + + def delete_load_balancer_network(self): + self._get_load_balancer_and_network() + self._get_load_balancer_network() + if self.hcloud_load_balancer_network is not None and self.hcloud_load_balancer is not None: + if not self.module.check_mode: + self.hcloud_load_balancer.detach_from_network( + self.hcloud_load_balancer_network.network).wait_until_finished() + self._mark_as_changed() + self.hcloud_load_balancer_network = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + load_balancer={"type": "str", "required": True}, + ip={"type": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerNetwork.define_module() + + hcloud = AnsibleHcloudLoadBalancerNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_load_balancer_network() + elif state == "present": + hcloud.present_load_balancer_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_load_balancer_service.py b/plugins/modules/hcloud_load_balancer_service.py new file mode 100644 index 0000000..269a703 --- /dev/null +++ b/plugins/modules/hcloud_load_balancer_service.py @@ -0,0 +1,609 @@ +#!/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 the services of cloud Load Balancers on the Hetzner Cloud. + + +description: + - Create, update and manage the services of cloud Load Balancers on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) +version_added: 0.1.0 +options: + load_balancer: + description: + - The Name of the Hetzner Cloud Load Balancer the service belongs to + type: str + required: true + listen_port: + description: + - The port the service listens on, i.e. the port users can connect to. + type: int + required: true + destination_port: + description: + - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on. + - Required if services does not exists and protocol is tcp. + type: int + protocol: + description: + - Protocol of the service. + - Required if Load Balancer does not exists. + type: str + choices: [ http, https, tcp ] + proxyprotocol: + description: + - Enable the PROXY protocol. + type: bool + http: + description: + - Configuration for HTTP and HTTPS services + type: dict + suboptions: + cookie_name: + description: + - Name of the cookie which will be set when you enable sticky sessions + type: str + cookie_lifetime: + description: + - Lifetime of the cookie which will be set when you enable sticky sessions, in seconds + type: int + certificates: + description: + - List of Names or IDs of certificates + type: list + elements: str + sticky_sessions: + description: + - Enable or disable sticky_sessions + type: bool + redirect_http: + description: + - Redirect Traffic from Port 80 to Port 443, only available if protocol is https + type: bool + health_check: + description: + - Configuration for health checks + type: dict + suboptions: + protocol: + description: + - Protocol the health checks will be performed over + type: str + choices: [ http, https, tcp ] + port: + description: + - Port the health check will be performed on + type: int + interval: + description: + - Interval of health checks, in seconds + type: int + timeout: + description: + - Timeout of health checks, in seconds + type: int + retries: + description: + - Number of retries until a target is marked as unhealthy + type: int + http: + description: + - Additional Configuration of health checks with protocol http/https + type: dict + suboptions: + domain: + description: + - Domain we will set within the HTTP HOST header + type: str + path: + description: + - Path we will try to access + type: str + response: + description: + - Response we expect, if response is not within the health check response the target is unhealthy + type: str + status_codes: + description: + - List of HTTP status codes we expect to get when we perform the health check. + type: list + elements: str + tls: + description: + - Verify the TLS certificate, only available if health check protocol is https + 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.1 +''' + +EXAMPLES = """ +- name: Create a basic Load Balancer service with Port 80 + hcloud_load_balancer_service: + load_balancer: my-load-balancer + protocol: http + listen_port: 80 + state: present + +- name: Ensure the Load Balancer is absent (remove if needed) + hcloud_load_balancer_service: + load_balancer: my-Load Balancer + protocol: http + listen_port: 80 + state: absent +""" + +RETURN = """ +hcloud_load_balancer_service: + description: The Load Balancer service instance + returned: Always + type: complex + contains: + load_balancer: + description: The name of the Load Balancer where the service belongs to + returned: always + type: str + sample: my-load-balancer + listen_port: + description: The port the service listens on, i.e. the port users can connect to. + returned: always + type: int + sample: 443 + protocol: + description: Protocol of the service + returned: always + type: str + sample: http + destination_port: + description: + - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on. + returned: always + type: int + sample: 80 + proxyprotocol: + description: + - Enable the PROXY protocol. + returned: always + type: bool + sample: false + http: + description: Configuration for HTTP and HTTPS services + returned: always + type: complex + contains: + cookie_name: + description: Name of the cookie which will be set when you enable sticky sessions + returned: always + type: str + sample: HCLBSTICKY + cookie_lifetime: + description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds + returned: always + type: int + sample: 3600 + certificates: + description: List of Names or IDs of certificates + returned: always + type: list + elements: str + sticky_sessions: + description: Enable or disable sticky_sessions + returned: always + type: bool + sample: true + redirect_http: + description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https + returned: always + type: bool + sample: false + health_check: + description: Configuration for health checks + returned: always + type: complex + contains: + protocol: + description: Protocol the health checks will be performed over + returned: always + type: str + sample: http + port: + description: Port the health check will be performed on + returned: always + type: int + sample: 80 + interval: + description: Interval of health checks, in seconds + returned: always + type: int + sample: 15 + timeout: + description: Timeout of health checks, in seconds + returned: always + type: int + sample: 10 + retries: + description: Number of retries until a target is marked as unhealthy + returned: always + type: int + sample: 3 + http: + description: Additional Configuration of health checks with protocol http/https + returned: always + type: complex + contains: + domain: + description: Domain we will set within the HTTP HOST header + returned: always + type: str + sample: example.com + path: + description: Path we will try to access + returned: always + type: str + sample: / + response: + description: Response we expect, if response is not within the health check response the target is unhealthy + returned: always + type: str + status_codes: + description: List of HTTP status codes we expect to get when we perform the health check. + returned: always + type: list + elements: str + sample: ["2??","3??"] + tls: + description: Verify the TLS certificate, only available if health check protocol is https + returned: always + type: bool + 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, LoadBalancerServiceHttp, \ + LoadBalancerServiceHealthCheck, LoadBalancerServiceHealthCheckHttp + 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): + http = None + if self.hcloud_load_balancer_service.protocol != "tcp": + http = { + "cookie_name": to_native(self.hcloud_load_balancer_service.http.cookie_name), + "cookie_lifetime": self.hcloud_load_balancer_service.http.cookie_name, + "redirect_http": self.hcloud_load_balancer_service.http.redirect_http, + "sticky_sessions": self.hcloud_load_balancer_service.http.sticky_sessions, + "certificates": [to_native(certificate.name) for certificate in + self.hcloud_load_balancer_service.http.certificates], + } + health_check = { + "protocol": to_native(self.hcloud_load_balancer_service.health_check.protocol), + "port": self.hcloud_load_balancer_service.health_check.port, + "interval": self.hcloud_load_balancer_service.health_check.interval, + "timeout": self.hcloud_load_balancer_service.health_check.timeout, + "retries": self.hcloud_load_balancer_service.health_check.retries, + } + if self.hcloud_load_balancer_service.health_check.protocol != "tcp": + health_check["http"] = { + "domain": to_native(self.hcloud_load_balancer_service.health_check.http.domain), + "path": to_native(self.hcloud_load_balancer_service.health_check.http.path), + "response": to_native(self.hcloud_load_balancer_service.health_check.http.response), + "certificates": [to_native(status_code) for status_code in + self.hcloud_load_balancer_service.health_check.http.status_codes], + "tls": self.hcloud_load_balancer_service.health_check.tls, + } + return { + "load_balancer": to_native(self.hcloud_load_balancer.name), + "protocol": to_native(self.hcloud_load_balancer_service.protocol), + "listen_port": self.hcloud_load_balancer_service.listen_port, + "destination_port": self.hcloud_load_balancer_service.destination_port, + "proxyprotocol": self.hcloud_load_balancer_service.proxyprotocol, + "http": http, + "health_check": health_check, + } + + 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_service(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"), + "listen_port": self.module.params.get("listen_port"), + "proxyprotocol": self.module.params.get("proxyprotocol") + } + + if self.module.params.get("destination_port"): + params["destination_port"] = self.module.params.get("destination_port") + + if self.module.params.get("http"): + params["http"] = self.__get_service_http(http_arg=self.module.params.get("http")) + + if self.module.params.get("health_check"): + params["health_check"] = self.__get_service_health_checks( + health_check=self.module.params.get("health_check")) + + if not self.module.check_mode: + try: + self.hcloud_load_balancer.add_service(LoadBalancerService(**params)).wait_until_finished( + max_retries=1000) + except APIException as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_load_balancer() + self._get_load_balancer_service() + + def __get_service_http(self, http_arg): + service_http = LoadBalancerServiceHttp(certificates=[]) + if http_arg.get("cookie_name") is not None: + service_http.cookie_name = http_arg.get("cookie_name") + if http_arg.get("cookie_lifetime") is not None: + service_http.cookie_lifetime = http_arg.get("cookie_lifetime") + if http_arg.get("sticky_sessions") is not None: + service_http.sticky_sessions = http_arg.get("sticky_sessions") + if http_arg.get("redirect_http") is not None: + service_http.redirect_http = http_arg.get("redirect_http") + if http_arg.get("certificates") is not None: + certificates = http_arg.get("certificates") + if certificates is not None: + for certificate in certificates: + hcloud_cert = None + try: + try: + hcloud_cert = self.client.certificates.get_by_name( + certificate + ) + except APIException: + hcloud_cert = self.client.certificates.get_by_id( + certificate + ) + except APIException as e: + self.module.fail_json(msg=e.message) + service_http.certificates.append(hcloud_cert) + + return service_http + + def __get_service_health_checks(self, health_check): + service_health_check = LoadBalancerServiceHealthCheck() + if health_check.get("protocol") is not None: + service_health_check.protocol = health_check.get("protocol") + if health_check.get("port") is not None: + service_health_check.port = health_check.get("port") + if health_check.get("interval") is not None: + service_health_check.interval = health_check.get("interval") + if health_check.get("timeout") is not None: + service_health_check.timeout = health_check.get("timeout") + if health_check.get("retries") is not None: + service_health_check.retries = health_check.get("retries") + if health_check.get("http") is not None: + health_check_http = health_check.get("http") + service_health_check.http = LoadBalancerServiceHealthCheckHttp() + if health_check_http.get("domain") is not None: + service_health_check.http.domain = health_check_http.get("domain") + if health_check_http.get("path") is not None: + service_health_check.http.path = health_check_http.get("path") + if health_check_http.get("response") is not None: + service_health_check.http.response = health_check_http.get("response") + if health_check_http.get("status_codes") is not None: + service_health_check.http.status_codes = health_check_http.get("status_codes") + if health_check_http.get("tls") is not None: + service_health_check.http.tls = health_check_http.get("tls") + + return service_health_check + + def _update_load_balancer_service(self): + changed = False + try: + params = { + "listen_port": self.module.params.get("listen_port"), + } + + if self.module.params.get("destination_port") is not None: + if self.hcloud_load_balancer_service.destination_port != self.module.params.get("destination_port"): + params["destination_port"] = self.module.params.get("destination_port") + changed = True + + if self.module.params.get("protocol") is not None: + if self.hcloud_load_balancer_service.protocol != self.module.params.get("protocol"): + params["protocol"] = self.module.params.get("protocol") + changed = True + + if self.module.params.get("proxyprotocol") is not None: + if self.hcloud_load_balancer_service.proxyprotocol != self.module.params.get("proxyprotocol"): + params["proxyprotocol"] = self.module.params.get("proxyprotocol") + changed = True + + if self.module.params.get("http") is not None: + params["http"] = self.__get_service_http(http_arg=self.module.params.get("http")) + changed = True + + if self.module.params.get("health_check") is not None: + params["health_check"] = self.__get_service_health_checks( + health_check=self.module.params.get("health_check")) + changed = True + + if not self.module.check_mode: + self.hcloud_load_balancer.update_service(LoadBalancerService(**params)).wait_until_finished( + max_retries=1000) + except APIException as e: + self.module.fail_json(msg=e.message) + self._get_load_balancer() + + if changed: + self._mark_as_changed() + + 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_service() + else: + self._update_load_balancer_service() + + 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).wait_until_finished( + max_retries=1000) + 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={ + "type": "str", + "choices": ["http", "https", "tcp"], + }, + proxyprotocol={"type": "bool", "default": False}, + http={ + "type": "dict", + "options": dict( + cookie_name={ + "type": "str" + }, + cookie_lifetime={ + "type": "int" + }, + sticky_sessions={ + "type": "bool", + "default": False + }, + redirect_http={ + "type": "bool", + "default": False + }, + certificates={ + "type": "list", + "elements": "str" + }, + + ) + }, + health_check={ + "type": "dict", + "options": dict( + protocol={ + "type": "str", + "choices": ["http", "https", "tcp"], + }, + port={ + "type": "int" + }, + interval={ + "type": "int" + }, + timeout={ + "type": "int" + }, + retries={ + "type": "int" + }, + http={ + "type": "dict", + "options": dict( + domain={ + "type": "str" + }, + path={ + "type": "str" + }, + response={ + "type": "str" + }, + status_codes={ + "type": "list", + "elements": "str" + }, + tls={ + "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/plugins/modules/hcloud_load_balancer_target.py b/plugins/modules/hcloud_load_balancer_target.py new file mode 100644 index 0000000..50dd00d --- /dev/null +++ b/plugins/modules/hcloud_load_balancer_target.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_target + +short_description: Manage Hetzner Cloud Load Balancer targets + + +description: + - Create and delete Hetzner Cloud Load Balancer targets + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 0.1.0 +options: + type: + description: + - The type of the target. + type: str + choices: [ server ] + required: true + load_balancer: + description: + - The name of the Hetzner Cloud Load Balancer. + type: str + required: true + server: + description: + - The name of the Hetzner Cloud Server. + - Required if I(type) is server + type: str + use_private_ip: + description: + - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network. + - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network) + type: bool + state: + description: + - State of the load_balancer_network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.8.1 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a server Load Balancer target + hcloud_load_balancer_target: + type: server + load_balancer: my-LoadBalancer + server: my-server + state: present + +- name: Ensure the Load Balancer target is absent (remove if needed) + hcloud_load_balancer_target: + type: server + load_balancer: my-LoadBalancer + server: my-server + state: absent +""" + +RETURN = """ +hcloud_load_balancer_target: + description: The relationship between a Load Balancer and a network + returned: always + type: complex + contains: + type: + description: Type of the Load Balancer Target + type: str + returned: always + sample: server + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-LoadBalancer + server: + description: Name of the Server + type: str + returned: if I(type) is server + sample: my-server +""" + +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 import APIException + from hcloud.load_balancers.domain import LoadBalancerTarget +except ImportError: + APIException = None + + +class AnsibleHcloudLoadBalancerTarget(Hcloud): + def __init__(self, module): + super(AnsibleHcloudLoadBalancerTarget, self).__init__(module, "hcloud_load_balancer_target") + self.hcloud_load_balancer = None + self.hcloud_load_balancer_target = None + self.hcloud_server = None + + def _prepare_result(self): + return { + "type": to_native(self.hcloud_load_balancer_target.type), + "load_balancer": to_native(self.hcloud_load_balancer.name), + "server": to_native(self.hcloud_server.name) if self.hcloud_server else None, + } + + def _get_load_balancer_and_target(self): + try: + self.hcloud_load_balancer = self.client.load_balancers.get_by_name(self.module.params.get("load_balancer")) + if self.module.params.get("type") == "server": + self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("server")) + self.hcloud_load_balancer_target = None + except APIException as e: + self.module.fail_json(msg=e.message) + + def _get_load_balancer_target(self): + for target in self.hcloud_load_balancer.targets: + if self.module.params.get("type") == "server": + if target.server.id == self.hcloud_server.id: + self.hcloud_load_balancer_target = target + + def _create_load_balancer_target(self): + params = { + "target": None + } + + if self.module.params.get("type") == "server": + self.module.fail_on_missing_params( + required_params=["server"] + ) + params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), server=self.hcloud_server, + use_private_ip=self.module.params.get("use_private_ip")) + if not self.module.check_mode: + try: + self.hcloud_load_balancer.add_target(**params).wait_until_finished() + except APIException as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_load_balancer_and_target() + self._get_load_balancer_target() + + def present_load_balancer_target(self): + self._get_load_balancer_and_target() + self._get_load_balancer_target() + if self.hcloud_load_balancer_target is None: + self._create_load_balancer_target() + + def delete_load_balancer_target(self): + self._get_load_balancer_and_target() + self._get_load_balancer_target() + if self.hcloud_load_balancer_target is not None and self.hcloud_load_balancer is not None: + if not self.module.check_mode: + target = None + if self.module.params.get("type") == "server": + target = LoadBalancerTarget(type=self.module.params.get("type"), + server=self.hcloud_server, + use_private_ip=self.module.params.get("use_private_ip")) + self.hcloud_load_balancer.remove_target(target).wait_until_finished() + self._mark_as_changed() + self.hcloud_load_balancer_target = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + type={"type": "str", "required": True, "choices": ["server"]}, + load_balancer={"type": "str", "required": True}, + server={"type": "str"}, + use_private_ip={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerTarget.define_module() + + hcloud = AnsibleHcloudLoadBalancerTarget(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_load_balancer_target() + elif state == "present": + hcloud.present_load_balancer_target() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_load_balancer_type_info.py b/plugins/modules/hcloud_load_balancer_type_info.py new file mode 100644 index 0000000..9645009 --- /dev/null +++ b/plugins/modules/hcloud_load_balancer_type_info.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_type_info + +short_description: Gather infos about the Hetzner Cloud Load Balancer types. + + +description: + - Gather infos about your Hetzner Cloud Load Balancer types. + +author: + - Lukas Kaemmerling (@LKaemmerling) +version_added: 0.1.0 +options: + id: + description: + - The ID of the Load Balancer type you want to get. + type: int + name: + description: + - The name of the Load Balancer type you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud Load Balancer type infos + hcloud_load_balancer_type_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_load_balancer_type_info +""" + +RETURN = """ +hcloud_load_balancer_type_info: + description: The Load Balancer type infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Load Balancer type + returned: always + type: int + sample: 1937415 + name: + description: Name of the Load Balancer type + returned: always + type: str + sample: lb11 + description: + description: Description of the Load Balancer type + returned: always + type: str + sample: LB11 + max_connections: + description: Number of maximum simultaneous open connections + returned: always + type: int + sample: 1 + max_services: + description: Number of services a Load Balancer of this type can have + returned: always + type: int + sample: 1 + max_targets: + description: Number of targets a single Load Balancer can have + returned: always + type: int + sample: 25 + max_assigned_certificates: + description: Number of SSL Certificates that can be assigned to a single Load Balancer + returned: always + type: int + sample: 5 +""" + +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 import APIException +except ImportError: + pass + + +class AnsibleHcloudLoadBalancerTypeInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_type_info") + self.hcloud_load_balancer_type_info = None + + def _prepare_result(self): + tmp = [] + + for load_balancer_type in self.hcloud_load_balancer_type_info: + if load_balancer_type is not None: + tmp.append({ + "id": to_native(load_balancer_type.id), + "name": to_native(load_balancer_type.name), + "description": to_native(load_balancer_type.description), + "max_connections": load_balancer_type.max_connections, + "max_services": load_balancer_type.max_services, + "max_targets": load_balancer_type.max_targets, + "max_assigned_certificates": load_balancer_type.max_assigned_certificates + }) + return tmp + + def get_load_balancer_types(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_load_balancer_type_info = self.client.load_balancer_types.get_all() + + except APIException as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerTypeInfo.define_module() + + hcloud = AnsibleHcloudLoadBalancerTypeInfo(module) + hcloud.get_load_balancer_types() + result = hcloud.get_result() + ansible_info = { + 'hcloud_load_balancer_type_info': result['hcloud_load_balancer_type_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/hcloud_network.py b/plugins/modules/hcloud_network.py index c511b5f..bad11a7 100644 --- a/plugins/modules/hcloud_network.py +++ b/plugins/modules/hcloud_network.py @@ -100,7 +100,7 @@ hcloud_network: type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" labels: description: User-defined labels (key-value pairs) type: dict diff --git a/plugins/modules/hcloud_network_info.py b/plugins/modules/hcloud_network_info.py index 05fecb9..b836379 100644 --- a/plugins/modules/hcloud_network_info.py +++ b/plugins/modules/hcloud_network_info.py @@ -173,7 +173,7 @@ hcloud_network_info: description: True if the network is protected for deletion returned: always type: bool - version_added: "1.0.0" + version_added: "0.1.0" labels: description: Labels of the network returned: always diff --git a/plugins/modules/hcloud_server.py b/plugins/modules/hcloud_server.py index 9cfe301..86ad04d 100644 --- a/plugins/modules/hcloud_server.py +++ b/plugins/modules/hcloud_server.py @@ -238,13 +238,13 @@ hcloud_server: type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" rebuild_protection: description: True if server is protected for rebuild type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" """ from ansible.module_utils.basic import AnsibleModule diff --git a/plugins/modules/hcloud_server_info.py b/plugins/modules/hcloud_server_info.py index 252f679..7cb47bd 100644 --- a/plugins/modules/hcloud_server_info.py +++ b/plugins/modules/hcloud_server_info.py @@ -116,13 +116,13 @@ hcloud_server_info: type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" rebuild_protection: description: True if server is protected for rebuild type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" """ from ansible.module_utils.basic import AnsibleModule diff --git a/plugins/modules/hcloud_server_network.py b/plugins/modules/hcloud_server_network.py index 545c2ed..e876834 100644 --- a/plugins/modules/hcloud_server_network.py +++ b/plugins/modules/hcloud_server_network.py @@ -25,7 +25,6 @@ options: network: description: - The name of the Hetzner Cloud Networks. - type: str required: true server: diff --git a/plugins/modules/hcloud_subnetwork.py b/plugins/modules/hcloud_subnetwork.py index 111db86..b3123f2 100644 --- a/plugins/modules/hcloud_subnetwork.py +++ b/plugins/modules/hcloud_subnetwork.py @@ -25,7 +25,6 @@ options: network: description: - The ID or Name of the Hetzner Cloud Networks. - type: str required: true ip_range: diff --git a/plugins/modules/hcloud_volume.py b/plugins/modules/hcloud_volume.py index be59f40..428cce3 100644 --- a/plugins/modules/hcloud_volume.py +++ b/plugins/modules/hcloud_volume.py @@ -134,7 +134,7 @@ hcloud_volume: returned: always type: str sample: /dev/disk/by-id/scsi-0HC_Volume_12345 - version_added: "1.0.0" + version_added: "0.1.0" location: description: Location name where the Volume is located at type: str @@ -157,7 +157,7 @@ hcloud_volume: type: bool returned: always sample: false - version_added: "1.0.0" + version_added: "0.1.0" """ from ansible.module_utils.basic import AnsibleModule diff --git a/plugins/modules/hcloud_volume_info.py b/plugins/modules/hcloud_volume_info.py index ded6331..5b7e530 100644 --- a/plugins/modules/hcloud_volume_info.py +++ b/plugins/modules/hcloud_volume_info.py @@ -73,7 +73,7 @@ hcloud_volume_info: returned: always type: str sample: /dev/disk/by-id/scsi-0HC_Volume_12345 - version_added: "1.0.0" + version_added: "0.1.0" location: description: Name of the location where the Volume resides in returned: always @@ -88,7 +88,7 @@ hcloud_volume_info: description: True if the Volume is protected for deletion returned: always type: bool - version_added: "1.0.0" + version_added: "0.1.0" labels: description: User-defined labels (key-value pairs) returned: always diff --git a/tests/integration/targets/hcloud_certificate/aliases b/tests/integration/targets/hcloud_certificate/aliases new file mode 100644 index 0000000..55ec821 --- /dev/null +++ b/tests/integration/targets/hcloud_certificate/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/tests/integration/targets/hcloud_certificate/defaults/main.yml b/tests/integration/targets/hcloud_certificate/defaults/main.yml new file mode 100644 index 0000000..49b6c6f --- /dev/null +++ b/tests/integration/targets/hcloud_certificate/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_certificate_name: "{{hcloud_prefix}}-integration" diff --git a/tests/integration/targets/hcloud_certificate/meta/main.yml b/tests/integration/targets/hcloud_certificate/meta/main.yml new file mode 100644 index 0000000..e531064 --- /dev/null +++ b/tests/integration/targets/hcloud_certificate/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - setup_selfsigned_certificate +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_certificate/tasks/main.yml b/tests/integration/targets/hcloud_certificate/tasks/main.yml new file mode 100644 index 0000000..5f6376e --- /dev/null +++ b/tests/integration/targets/hcloud_certificate/tasks/main.yml @@ -0,0 +1,123 @@ +# 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 certificate + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create certificate + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: certificate, private_key"' + +- name: test create certificate with check mode + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + register: result + check_mode: yes +- name: test create certificate with check mode + assert: + that: + - result is changed + +- name: test create certificate + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + labels: + key: value + my-label: label + register: certificate +- name: verify create certificate + assert: + that: + - certificate is changed + - certificate.hcloud_certificate.name == "{{ hcloud_certificate_name }}" + - certificate.hcloud_certificate.domain_names[0] == "www.example.com" + - certificate.hcloud_certificate.labels.key == "value" + +- name: test create certificate idempotence + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + register: result +- name: verify create certificate idempotence + assert: + that: + - result is not changed + +- name: test update certificate with check mode + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + register: result + check_mode: yes +- name: test create certificate with check mode + assert: + that: + - result is changed + +- name: test update certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + labels: + key: value + register: result +- name: test update certificate + assert: + that: + - result is changed + - result.hcloud_certificate.name == "changed-{{ hcloud_certificate_name }}" + +- name: test update certificate with same labels + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + labels: + key: value + register: result +- name: test update certificate with same labels + assert: + that: + - result is not changed + +- name: test update certificate with other labels + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + labels: + key: value + test: "val123" + register: result +- name: test update certificate with other labels + assert: + that: + - result is changed + +- name: test rename certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "{{ hcloud_certificate_name }}" + register: result +- name: test rename certificate + assert: + that: + - result is changed + - result.hcloud_certificate.name == "{{ hcloud_certificate_name }}" + +- name: absent certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success diff --git a/tests/integration/targets/hcloud_certificate_info/aliases b/tests/integration/targets/hcloud_certificate_info/aliases new file mode 100644 index 0000000..55ec821 --- /dev/null +++ b/tests/integration/targets/hcloud_certificate_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/tests/integration/targets/hcloud_certificate_info/defaults/main.yml b/tests/integration/targets/hcloud_certificate_info/defaults/main.yml new file mode 100644 index 0000000..6205b19 --- /dev/null +++ b/tests/integration/targets/hcloud_certificate_info/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_certificate_name: "always-there-cert" diff --git a/tests/integration/targets/hcloud_certificate_info/meta/main.yml b/tests/integration/targets/hcloud_certificate_info/meta/main.yml new file mode 100644 index 0000000..f830a9d --- /dev/null +++ b/tests/integration/targets/hcloud_certificate_info/meta/main.yml @@ -0,0 +1,2 @@ +collections: + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_certificate_info/tasks/main.yml b/tests/integration/targets/hcloud_certificate_info/tasks/main.yml new file mode 100644 index 0000000..7dc4eb7 --- /dev/null +++ b/tests/integration/targets/hcloud_certificate_info/tasks/main.yml @@ -0,0 +1,39 @@ +# 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: test gather hcloud certificate infos in check mode + hcloud_certificate_info: + register: hcloud_certificate + check_mode: yes +- name: verify test gather hcloud certificate infos in check mode + assert: + that: + - hcloud_certificate.hcloud_certificate_info| list | count >= 1 + +- name: test gather hcloud certificate infos + hcloud_certificate_info: + register: hcloud_certificate + check_mode: yes +- name: verify test gather hcloud certificate infos + assert: + that: + - hcloud_certificate.hcloud_certificate_info| list | count >= 1 + +- name: test gather hcloud certificate infos with correct label selector + hcloud_certificate_info: + label_selector: "key=value" + register: hcloud_certificate +- name: verify test gather hcloud certificate infos with correct label selector + assert: + that: + - hcloud_certificate.hcloud_certificate_info|selectattr('name','equalto','{{ hcloud_certificate_name }}') | list | count == 1 + +- name: test gather hcloud certificate infos with wrong label selector + hcloud_certificate_info: + label_selector: "key!=value" + register: hcloud_certificate +- name: verify test gather hcloud certificate infos with wrong label selector + assert: + that: + - hcloud_certificate.hcloud_certificate_info | list | count == 0 diff --git a/tests/integration/targets/hcloud_load_balancer/aliases b/tests/integration/targets/hcloud_load_balancer/aliases new file mode 100644 index 0000000..18dc30b --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/tests/integration/targets/hcloud_load_balancer/defaults/main.yml b/tests/integration/targets/hcloud_load_balancer/defaults/main.yml new file mode 100644 index 0000000..38e96f6 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2020, 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}}-integration" diff --git a/tests/integration/targets/hcloud_load_balancer/meta/main.yml b/tests/integration/targets/hcloud_load_balancer/meta/main.yml new file mode 100644 index 0000000..407c901 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_load_balancer/tasks/main.yml b/tests/integration/targets/hcloud_load_balancer/tasks/main.yml new file mode 100644 index 0000000..fd187b4 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer/tasks/main.yml @@ -0,0 +1,176 @@ +# 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: setup + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify setup + assert: + that: + - result is success +- name: test missing required parameters on create Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create Load Balancer + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: load_balancer_type"' + +- name: test create Load Balancer with check mode + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + load_balancer_type: lb11 + network_zone: eu-central + state: present + register: result + check_mode: yes +- name: test create Load Balancer Load Balancer + assert: + that: + - result is changed + +- name: test create Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + state: present + register: main_load_balancer +- name: verify create Load Balancer + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.name == "{{ hcloud_load_balancer_name }}" + - main_load_balancer.hcloud_load_balancer.load_balancer_type == "lb11" + +- name: test create Load Balancer idempotence + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: present + register: result +- name: verify create Load Balancer idempotence + assert: + that: + - result is not changed + +- name: test update Load Balancer protection + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + delete_protection: true + state: present + register: result_after_test + ignore_errors: true +- name: verify update Load Balancer protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_load_balancer.delete_protection is sameas true + +- name: test Load Balancer without protection set to be idempotent + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + register: result_after_test +- name: verify test Load Balancer without protection set to be idempotent + assert: + that: + - result_after_test is not changed + - result_after_test.hcloud_load_balancer.delete_protection is sameas true + +- name: test delete Load Balancer fails if it is protected + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Load Balancer fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "load balancer deletion is protected"' + +- name: test remove Load Balancer protection + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + delete_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify remove Load Balancer protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_load_balancer.delete_protection is sameas false + +- name: absent Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify absent Load Balancer + assert: + that: + - result is success + +- name: test create Load Balancer with labels + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + key: value + mylabel: "val123" + state: present + register: main_load_balancer +- name: verify create Load Balancer with labels + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.labels.key == "value" + - main_load_balancer.hcloud_load_balancer.labels.mylabel == "val123" + +- name: test update Load Balancer with labels + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + key: other + mylabel: "val123" + state: present + register: main_load_balancer +- name: verify update Load Balancer with labels + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.labels.key == "other" + - main_load_balancer.hcloud_load_balancer.labels.mylabel == "val123" + +- name: test update Load Balancer with labels in other order + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + mylabel: "val123" + key: other + state: present + register: main_load_balancer +- name: verify update Load Balancer with labels in other order + assert: + that: + - main_load_balancer is not changed + +- name: cleanup with labels + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/tests/integration/targets/hcloud_load_balancer_network/aliases b/tests/integration/targets/hcloud_load_balancer_network/aliases new file mode 100644 index 0000000..18dc30b --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_network/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml b/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml new file mode 100644 index 0000000..8747bff --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml @@ -0,0 +1,6 @@ +# 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_network_name: "{{hcloud_prefix}}-load_balancer-network" +hcloud_load_balancer_name: "{{hcloud_prefix}}-load_balancer-network" diff --git a/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml b/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml new file mode 100644 index 0000000..407c901 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml b/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml new file mode 100644 index 0000000..7f16225 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml @@ -0,0 +1,155 @@ +# 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 network + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/8" + state: present + register: network +- name: verify setup network + assert: + that: + - network is success + +- name: setup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "cloud" + network_zone: "eu-central" + state: present + register: subnetwork +- name: verify subnetwork + assert: + that: + - subnetwork is success + +- 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 missing required parameters on create load_balancer network + hcloud_load_balancer_network: + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create load_balancer network + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: load_balancer, network"' + +- name: test create load_balancer network with checkmode + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: present + register: result + check_mode: yes +- name: verify test create load_balancer network with checkmode + assert: + that: + - result is changed + +- name: test create load_balancer network + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: present + register: load_balancerNetwork +- name: verify create load_balancer network + assert: + that: + - load_balancerNetwork is changed + - load_balancerNetwork.hcloud_load_balancer_network.network == hcloud_network_name + - load_balancerNetwork.hcloud_load_balancer_network.load_balancer == hcloud_load_balancer_name + +- name: test create load_balancer network idempotency + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: present + register: load_balancerNetwork +- name: verify create load_balancer network idempotency + assert: + that: + - load_balancerNetwork is not changed + +- name: test absent load_balancer network + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: absent + register: result +- name: verify test absent load_balancer network + assert: + that: + - result is changed + +- name: test create load_balancer network with specified ip + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + ip: "10.0.0.2" + state: present + register: load_balancerNetwork +- name: verify create load_balancer network with specified ip + assert: + that: + - load_balancerNetwork is changed + - load_balancerNetwork.hcloud_load_balancer_network.network == hcloud_network_name + - load_balancerNetwork.hcloud_load_balancer_network.load_balancer == hcloud_load_balancer_name + - load_balancerNetwork.hcloud_load_balancer_network.ip == "10.0.0.2" + +- name: cleanup create load_balancer network with specified ip + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: absent + register: result +- name: verify cleanup create load_balancer network with specified ip + 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 subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "load_balancer" + network_zone: "eu-central" + state: absent + register: result +- name: verify cleanup subnetwork + assert: + that: + - result is changed + +- name: cleanup + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success 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..101effc --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml @@ -0,0 +1,112 @@ +# 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" + listen_port: 80 + 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" + listen_port: 80 + 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: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: present + register: load_balancer_service +- name: verify create load_balancer service idempotency + assert: + that: + - load_balancer_service is not changed + +- name: test update load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "tcp" + listen_port: 80 + 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 == "tcp" + - 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 absent load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: absent + register: result +- name: verify test absent load_balancer service + assert: + that: + - result is changed + +- name: test create load_balancer service with http + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + http: + cookie_name: "Test" + sticky_sessions: yes + 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: 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 diff --git a/tests/integration/targets/hcloud_load_balancer_target/aliases b/tests/integration/targets/hcloud_load_balancer_target/aliases new file mode 100644 index 0000000..18dc30b --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_target/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml b/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml new file mode 100644 index 0000000..9061af9 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml @@ -0,0 +1,6 @@ +# 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_server_name: "{{hcloud_prefix}}-lb-target" +hcloud_load_balancer_name: "{{hcloud_prefix}}-load_balancer-target" diff --git a/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml b/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml new file mode 100644 index 0000000..407c901 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml b/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml new file mode 100644 index 0000000..65fb9b0 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml @@ -0,0 +1,99 @@ +# 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 server + hcloud_server: + name: "{{hcloud_server_name}}" + server_type: cx11 + image: ubuntu-20.04 + state: started + location: "fsn1" + register: server +- name: verify setup server + assert: + that: + - server is success + +- 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 target with checkmode + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: result + check_mode: yes +- name: verify test create load_balancer target with checkmode + assert: + that: + - result is changed + +- name: test create load_balancer target + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: load_balancer_target +- name: verify create load_balancer target + assert: + that: + - 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: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: load_balancer_target +- name: verify create load_balancer target idempotency + assert: + that: + - load_balancer_target is not changed + +- name: test absent load_balancer target + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify test absent load_balancer target + 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_type_info/aliases b/tests/integration/targets/hcloud_load_balancer_type_info/aliases new file mode 100644 index 0000000..55ec821 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_type_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml b/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml new file mode 100644 index 0000000..b7fd863 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_load_balancer_type_name: "lb11" +hcloud_load_balancer_type_id: 1 diff --git a/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml b/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml new file mode 100644 index 0000000..407c901 --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml b/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml new file mode 100644 index 0000000..bcd805a --- /dev/null +++ b/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml @@ -0,0 +1,38 @@ +# 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 gather hcloud Load Balancer type infos + hcloud_load_balancer_type_info: + register: hcloud_load_balancer_types +- name: verify test gather hcloud Load Balancer type infos + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info| list | count >= 1 + +- name: test gather hcloud Load Balancer type infos in check mode + hcloud_load_balancer_type_info: + check_mode: yes + register: hcloud_load_balancer_types + +- name: verify test gather hcloud Load Balancer type infos in check mode + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info| list | count >= 1 + +- name: test gather hcloud Load Balancer type infos with name + hcloud_load_balancer_type_info: + name: "{{hcloud_load_balancer_type_name}}" + register: hcloud_load_balancer_types +- name: verify test gather hcloud Load Balancer type with name + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info|selectattr('name','equalto','{{ hcloud_load_balancer_type_name }}') | list | count == 1 + +- name: test gather hcloud Load Balancer type infos with correct id + hcloud_load_balancer_type_info: + id: "{{hcloud_load_balancer_type_id}}" + register: hcloud_load_balancer_types +- name: verify test gather hcloud Load Balancer type with correct id + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info|selectattr('name','equalto','{{ hcloud_load_balancer_type_name }}') | list | count == 1 diff --git a/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml b/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml new file mode 100644 index 0000000..27defe4 --- /dev/null +++ b/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml @@ -0,0 +1,27 @@ +# 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: create a cert temp file + tempfile: + state: file + register: certificate_example_com + tags: + - prepare +- name: create a key temp file + tempfile: + state: file + register: certificate_example_com_key + tags: + - prepare + - +- name: generate certificate + shell: openssl req -nodes -new -x509 -keyout {{ certificate_example_com_key.path }} -out {{ certificate_example_com.path }} -subj "/C=DE/ST=Munich/L=Bavaria/O=Dis/CN=www.example.com" + tags: + - prepare + +- name: set facts for future roles + set_fact: + certificate_example_com: "{{ lookup('file',certificate_example_com.path) }}" + certificate_example_com_key: "{{ lookup('file',certificate_example_com_key.path) }}" + tags: + - prepare diff --git a/tests/utils/gitlab/gitlab.sh b/tests/utils/gitlab/gitlab.sh index 8f4439d..f4a870a 100755 --- a/tests/utils/gitlab/gitlab.sh +++ b/tests/utils/gitlab/gitlab.sh @@ -55,7 +55,7 @@ retry ansible-galaxy -vvv collection install community.general retry ansible-galaxy -vvv collection install ansible.netcommon retry ansible-galaxy -vvv collection install community.internal_test_tools retry pip install netaddr --disable-pip-version-check -retry pip install hcloud +retry python -m pip install hcloud # END: HACK export PYTHONIOENCODING='utf-8' diff --git a/tests/utils/gitlab/integration.sh b/tests/utils/gitlab/integration.sh index 54c9859..4ab2e58 100755 --- a/tests/utils/gitlab/integration.sh +++ b/tests/utils/gitlab/integration.sh @@ -12,4 +12,4 @@ hcloud_api_token=${HCLOUD_TOKEN} export SHIPPABLE="true" export SHIPPABLE_BUILD_NUMBER="gl-$(cat prefix.txt)" export SHIPPABLE_JOB_NUMBER="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 2 | head -n 1)" -ansible-test integration --color --local -v "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} +ansible-test integration --color --local -vvvvv "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"}