From 7fec0b69a082aaeec72fee0612766aa42f6b1b4d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 18 Apr 2024 07:52:35 +0200 Subject: Merging upstream version 9.4.0+dfsg. Signed-off-by: Daniel Baumann --- .../hetzner/hcloud/plugins/modules/certificate.py | 289 +++++++ .../hcloud/plugins/modules/certificate_info.py | 162 ++++ .../hcloud/plugins/modules/datacenter_info.py | 192 +++++ .../hetzner/hcloud/plugins/modules/firewall.py | 438 ++++++++++ .../hcloud/plugins/modules/firewall_info.py | 246 ++++++ .../hcloud/plugins/modules/firewall_resource.py | 243 ++++++ .../hetzner/hcloud/plugins/modules/floating_ip.py | 340 ++++++++ .../hcloud/plugins/modules/floating_ip_info.py | 180 ++++ .../hcloud/plugins/modules/hcloud_certificate.py | 291 ------- .../plugins/modules/hcloud_certificate_info.py | 162 ---- .../plugins/modules/hcloud_datacenter_facts.py | 160 ---- .../plugins/modules/hcloud_datacenter_info.py | 160 ---- .../hcloud/plugins/modules/hcloud_firewall.py | 359 -------- .../hcloud/plugins/modules/hcloud_floating_ip.py | 355 -------- .../plugins/modules/hcloud_floating_ip_facts.py | 185 ---- .../plugins/modules/hcloud_floating_ip_info.py | 185 ---- .../hcloud/plugins/modules/hcloud_image_facts.py | 219 ----- .../hcloud/plugins/modules/hcloud_image_info.py | 219 ----- .../hcloud/plugins/modules/hcloud_load_balancer.py | 318 ------- .../plugins/modules/hcloud_load_balancer_info.py | 398 --------- .../modules/hcloud_load_balancer_network.py | 209 ----- .../modules/hcloud_load_balancer_service.py | 620 -------------- .../plugins/modules/hcloud_load_balancer_target.py | 321 ------- .../modules/hcloud_load_balancer_type_info.py | 158 ---- .../plugins/modules/hcloud_location_facts.py | 159 ---- .../hcloud/plugins/modules/hcloud_location_info.py | 159 ---- .../hcloud/plugins/modules/hcloud_network.py | 243 ------ .../hcloud/plugins/modules/hcloud_network_info.py | 293 ------- .../plugins/modules/hcloud_placement_group.py | 230 ----- .../hcloud/plugins/modules/hcloud_primary_ip.py | 271 ------ .../hetzner/hcloud/plugins/modules/hcloud_rdns.py | 360 -------- .../hetzner/hcloud/plugins/modules/hcloud_route.py | 196 ----- .../hcloud/plugins/modules/hcloud_server.py | 928 -------------------- .../hcloud/plugins/modules/hcloud_server_facts.py | 244 ------ .../hcloud/plugins/modules/hcloud_server_info.py | 244 ------ .../plugins/modules/hcloud_server_network.py | 246 ------ .../plugins/modules/hcloud_server_type_facts.py | 183 ---- .../plugins/modules/hcloud_server_type_info.py | 183 ---- .../hcloud/plugins/modules/hcloud_ssh_key.py | 243 ------ .../hcloud/plugins/modules/hcloud_ssh_key_facts.py | 169 ---- .../hcloud/plugins/modules/hcloud_ssh_key_info.py | 169 ---- .../hcloud/plugins/modules/hcloud_subnetwork.py | 247 ------ .../hcloud/plugins/modules/hcloud_volume.py | 340 -------- .../hcloud/plugins/modules/hcloud_volume_facts.py | 186 ---- .../hcloud/plugins/modules/hcloud_volume_info.py | 186 ---- .../hetzner/hcloud/plugins/modules/image_info.py | 209 +++++ .../hetzner/hcloud/plugins/modules/iso_info.py | 206 +++++ .../hcloud/plugins/modules/load_balancer.py | 340 ++++++++ .../hcloud/plugins/modules/load_balancer_info.py | 424 +++++++++ .../plugins/modules/load_balancer_network.py | 203 +++++ .../plugins/modules/load_balancer_service.py | 578 +++++++++++++ .../hcloud/plugins/modules/load_balancer_target.py | 313 +++++++ .../plugins/modules/load_balancer_type_info.py | 161 ++++ .../hcloud/plugins/modules/location_info.py | 145 ++++ .../hetzner/hcloud/plugins/modules/network.py | 259 ++++++ .../hetzner/hcloud/plugins/modules/network_info.py | 296 +++++++ .../hcloud/plugins/modules/placement_group.py | 220 +++++ .../hetzner/hcloud/plugins/modules/primary_ip.py | 260 ++++++ .../hcloud/plugins/modules/primary_ip_info.py | 203 +++++ .../hetzner/hcloud/plugins/modules/rdns.py | 360 ++++++++ .../hetzner/hcloud/plugins/modules/route.py | 190 ++++ .../hetzner/hcloud/plugins/modules/server.py | 952 +++++++++++++++++++++ .../hetzner/hcloud/plugins/modules/server_info.py | 238 ++++++ .../hcloud/plugins/modules/server_network.py | 246 ++++++ .../hcloud/plugins/modules/server_type_info.py | 204 +++++ .../hetzner/hcloud/plugins/modules/ssh_key.py | 234 +++++ .../hetzner/hcloud/plugins/modules/ssh_key_info.py | 156 ++++ .../hetzner/hcloud/plugins/modules/subnetwork.py | 236 +++++ .../hetzner/hcloud/plugins/modules/volume.py | 331 +++++++ .../hetzner/hcloud/plugins/modules/volume_info.py | 174 ++++ 70 files changed, 9228 insertions(+), 9798 deletions(-) create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/certificate.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/certificate_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/datacenter_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/firewall.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/firewall_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/firewall_resource.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/floating_ip.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/floating_ip_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py delete mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/image_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/iso_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/load_balancer.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_network.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_service.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_target.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_type_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/location_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/network.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/network_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/placement_group.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/primary_ip.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/primary_ip_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/rdns.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/route.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/server.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/server_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/server_network.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/server_type_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/ssh_key.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/ssh_key_info.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/subnetwork.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/volume.py create mode 100644 ansible_collections/hetzner/hcloud/plugins/modules/volume_info.py (limited to 'ansible_collections/hetzner/hcloud/plugins/modules') diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/certificate.py b/ansible_collections/hetzner/hcloud/plugins/modules/certificate.py new file mode 100644 index 000000000..ea39be6ca --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/certificate.py @@ -0,0 +1,289 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: 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 exist. + 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 exist and I(type=uploaded). + type: str + private_key: + description: + - Certificate key in PEM format. + - Required if certificate does not exist and I(type=uploaded). + type: str + domain_names: + description: + - Domains and subdomains that should be contained in the Certificate issued by Let's Encrypt. + - Required if I(type=managed). + type: list + default: [ ] + elements: str + type: + description: + - Choose between uploading a Certificate in PEM format or requesting a managed Let's Encrypt Certificate. + default: uploaded + choices: [ uploaded, managed ] + 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 + hetzner.hcloud.certificate: + name: my-certificate + certificate: -----BEGIN CERTIFICATE-----... + private_key: -----BEGIN PRIVATE KEY-----... + state: present + +- name: Create a certificate with labels + hetzner.hcloud.certificate: + name: my-certificate + certificate: -----BEGIN CERTIFICATE-----... + private_key: -----BEGIN PRIVATE KEY-----... + labels: + key: value + mylabel: 123 + state: present + +- name: Create a managed certificate + hetzner.hcloud.certificate: + name: my-certificate + type: managed + domain_names: + - example.com + - www.example.com + state: present + +- name: Ensure the certificate is absent (remove if needed) + hetzner.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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.certificates import BoundCertificate + + +class AnsibleHCloudCertificate(AnsibleHCloud): + represent = "hcloud_certificate" + + hcloud_certificate: BoundCertificate | None = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_certificate.id), + "name": to_native(self.hcloud_certificate.name), + "type": to_native(self.hcloud_certificate.type), + "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 HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_certificate(self): + self.module.fail_on_missing_params(required_params=["name"]) + + params = { + "name": self.module.params.get("name"), + "labels": self.module.params.get("labels"), + } + if self.module.params.get("type") == "uploaded": + self.module.fail_on_missing_params(required_params=["certificate", "private_key"]) + params["certificate"] = self.module.params.get("certificate") + params["private_key"] = self.module.params.get("private_key") + if not self.module.check_mode: + try: + self.client.certificates.create(**params) + except HCloudException as exception: + self.fail_json_hcloud(exception) + else: + self.module.fail_on_missing_params(required_params=["domain_names"]) + params["domain_names"] = self.module.params.get("domain_names") + if not self.module.check_mode: + try: + resp = self.client.certificates.create_managed(**params) + resp.action.wait_until_finished(max_retries=1000) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + self._mark_as_changed() + self._get_certificate() + + def _update_certificate(self): + try: + 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() + except HCloudException as exception: + self.fail_json_hcloud(exception) + 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: + try: + self.client.certificates.delete(self.hcloud_certificate) + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_certificate = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + type={ + "choices": ["uploaded", "managed"], + "default": "uploaded", + }, + domain_names={"type": "list", "elements": "str", "default": []}, + certificate={"type": "str"}, + private_key={"type": "str", "no_log": True}, + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().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/ansible_collections/hetzner/hcloud/plugins/modules/certificate_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/certificate_info.py new file mode 100644 index 000000000..e074046fd --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/certificate_info.py @@ -0,0 +1,162 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: 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. + - The module will fail if the provided ID is invalid. + 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 + hetzner.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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.certificates import BoundCertificate + + +class AnsibleHCloudCertificateInfo(AnsibleHCloud): + represent = "hcloud_certificate_info" + + hcloud_certificate_info: list[BoundCertificate] | None = 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 HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = 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/ansible_collections/hetzner/hcloud/plugins/modules/datacenter_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/datacenter_info.py new file mode 100644 index 000000000..f6665a6fb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/datacenter_info.py @@ -0,0 +1,192 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: datacenter_info + +short_description: Gather info about the Hetzner Cloud datacenters. + +description: + - Gather info about your Hetzner Cloud datacenters. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the datacenter you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the datacenter you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud datacenter info + hetzner.hcloud.datacenter_info: + register: output + +- name: Print the gathered info + debug: + var: output + +- name: List available server_types in a datacenter + block: + - name: Gather a hcloud datacenter + hetzner.hcloud.datacenter_info: + name: fsn1-dc14 + register: output + + - name: Gather a hcloud datacenter available server_types + hetzner.hcloud.server_type_info: + id: "{{ item }}" + loop: "{{ output.hcloud_datacenter_info[0].server_types.available }}" + register: available_server_types + + - name: Print a hcloud datacenter available server_types + ansible.builtin.debug: + var: available_server_types.results | map(attribute='hcloud_server_type_info') +""" + +RETURN = """ +hcloud_datacenter_info: + description: + - The datacenter info as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the datacenter + returned: always + type: int + sample: 1937415 + name: + description: Name of the datacenter + returned: always + type: str + sample: fsn1-dc8 + description: + description: Detail description of the datacenter + returned: always + type: str + sample: Falkenstein DC 8 + location: + description: Name of the location where the datacenter resides in + returned: always + type: str + sample: fsn1 + city: + description: City of the location + returned: always + type: str + sample: fsn1 + server_types: + description: The Server types the Datacenter can handle + returned: always + type: dict + contains: + available: + description: IDs of Server types that are supported and for which the Datacenter has enough resources left + returned: always + type: list + elements: int + sample: [1, 2, 3] + available_for_migration: + description: IDs of Server types that are supported and for which the Datacenter has enough resources left + returned: always + type: list + elements: int + sample: [1, 2, 3] + supported: + description: IDs of Server types that are supported in the Datacenter + returned: always + type: list + elements: int + sample: [1, 2, 3] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.datacenters import BoundDatacenter + + +class AnsibleHCloudDatacenterInfo(AnsibleHCloud): + represent = "hcloud_datacenter_info" + + hcloud_datacenter_info: list[BoundDatacenter] | None = None + + def _prepare_result(self): + tmp = [] + + for datacenter in self.hcloud_datacenter_info: + if datacenter is None: + continue + + tmp.append( + { + "id": to_native(datacenter.id), + "name": to_native(datacenter.name), + "description": to_native(datacenter.description), + "location": to_native(datacenter.location.name), + "server_types": { + "available": [o.id for o in datacenter.server_types.available], + "available_for_migration": [o.id for o in datacenter.server_types.available_for_migration], + "supported": [o.id for o in datacenter.server_types.supported], + }, + } + ) + + return tmp + + def get_datacenters(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_datacenter_info = [self.client.datacenters.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_datacenter_info = [self.client.datacenters.get_by_name(self.module.params.get("name"))] + else: + self.hcloud_datacenter_info = self.client.datacenters.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudDatacenterInfo.define_module() + hcloud = AnsibleHCloudDatacenterInfo(module) + + hcloud.get_datacenters() + result = hcloud.get_result() + + ansible_info = {"hcloud_datacenter_info": result["hcloud_datacenter_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/firewall.py b/ansible_collections/hetzner/hcloud/plugins/modules/firewall.py new file mode 100644 index 000000000..3c51b5c0a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/firewall.py @@ -0,0 +1,438 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: firewall +short_description: Create and manage firewalls on the Hetzner Cloud. + +description: + - Create, update and manage firewalls on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud Firewall to manage. + - Only required if no firewall O(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Firewall to manage. + - Only required if no firewall O(id) is given, or the firewall does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs). + type: dict + rules: + description: + - List of rules the firewall contain. + type: list + elements: dict + suboptions: + description: + description: + - User defined description of this rule. + type: str + direction: + description: + - The direction of the firewall rule. + type: str + choices: [in, out] + protocol: + description: + - The protocol of the firewall rule. + type: str + choices: [icmp, tcp, udp, esp, gre] + port: + description: + - The port or port range allowed by this rule. + - A port range can be specified by separating two ports with a dash, e.g 1024-5000. + - Only used if O(rules[].protocol=tcp) or O(rules[].protocol=udp). + type: str + source_ips: + description: + - List of CIDRs that are allowed within this rule. + - Use 0.0.0.0/0 to allow all IPv4 addresses and ::/0 to allow all IPv6 addresses. + - Only used if O(rules[].direction=in). + type: list + elements: str + default: [] + destination_ips: + description: + - List of CIDRs that are allowed within this rule. + - Use 0.0.0.0/0 to allow all IPv4 addresses and ::/0 to allow all IPv6 addresses. + - Only used if O(rules[].direction=out). + type: list + elements: str + default: [] + force: + description: + - Force the deletion of the Firewall when still in use. + type: bool + default: false + state: + description: + - State of the firewall. + default: present + choices: [absent, present] + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic firewall + hetzner.hcloud.firewall: + name: my-firewall + state: present + +- name: Create a firewall with rules + hetzner.hcloud.firewall: + name: my-firewall + rules: + - description: allow icmp from everywhere + direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + state: present + +- name: Create a firewall with labels + hetzner.hcloud.firewall: + name: my-firewall + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the firewall is absent (remove if needed) + hetzner.hcloud.firewall: + name: my-firewall + state: absent +""" + +RETURN = """ +hcloud_firewall: + description: The firewall instance. + returned: always + type: dict + contains: + id: + description: Numeric identifier of the firewall. + returned: always + type: int + sample: 1937415 + name: + description: Name of the firewall. + returned: always + type: str + sample: my-firewall + labels: + description: User-defined labels (key-value pairs). + returned: always + type: dict + rules: + description: List of rules the firewall contain. + returned: always + type: list + elements: dict + contains: + description: + description: User defined description of this rule. + type: str + returned: always + sample: allow http from anywhere + direction: + description: The direction of the firewall rule. + type: str + returned: always + sample: in + protocol: + description: The protocol of the firewall rule. + type: str + returned: always + sample: tcp + port: + description: The port or port range allowed by this rule. + type: str + returned: if RV(hcloud_firewall.rules[].protocol=tcp) or RV(hcloud_firewall.rules[].protocol=udp) + sample: "80" + source_ips: + description: List of source CIDRs that are allowed within this rule. + type: list + elements: str + returned: always + sample: ["0.0.0.0/0", "::/0"] + destination_ips: + description: List of destination CIDRs that are allowed within this rule. + type: list + elements: str + returned: always + sample: [] + applied_to: + description: List of Resources the Firewall is applied to. + returned: always + type: list + elements: dict + contains: + type: + description: Type of the resource. + type: str + choices: [server, label_selector] + sample: label_selector + server: + description: ID of the server. + type: int + sample: 12345 + label_selector: + description: Label selector value. + type: str + sample: env=prod + applied_to_resources: + description: List of Resources the Firewall label selector is applied to. + returned: if RV(hcloud_firewall.applied_to[].type=label_selector) + type: list + elements: dict + contains: + type: + description: Type of resource referenced. + type: str + choices: [server] + sample: server + server: + description: ID of the Server. + type: int + sample: 12345 +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import APIException, HCloudException +from ..module_utils.vendor.hcloud.firewalls import ( + BoundFirewall, + FirewallResource, + FirewallRule, +) + + +class AnsibleHCloudFirewall(AnsibleHCloud): + represent = "hcloud_firewall" + + hcloud_firewall: BoundFirewall | None = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_firewall.id), + "name": to_native(self.hcloud_firewall.name), + "rules": [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules], + "labels": self.hcloud_firewall.labels, + "applied_to": [self._prepare_result_applied_to(resource) for resource in self.hcloud_firewall.applied_to], + } + + def _prepare_result_rule(self, rule: FirewallRule): + return { + "direction": to_native(rule.direction), + "protocol": to_native(rule.protocol), + "port": to_native(rule.port) if rule.port is not None else None, + "source_ips": [to_native(cidr) for cidr in rule.source_ips], + "destination_ips": [to_native(cidr) for cidr in rule.destination_ips], + "description": to_native(rule.description) if rule.description is not None else None, + } + + def _prepare_result_applied_to(self, resource: FirewallResource): + result = { + "type": to_native(resource.type), + "server": to_native(resource.server.id) if resource.server is not None else None, + "label_selector": ( + to_native(resource.label_selector.selector) if resource.label_selector is not None else None + ), + } + if resource.applied_to_resources is not None: + result["applied_to_resources"] = [ + { + "type": to_native(item.type), + "server": to_native(item.server.id) if item.server is not None else None, + } + for item in resource.applied_to_resources + ] + return result + + def _get_firewall(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_firewall = self.client.firewalls.get_by_id(self.module.params.get("id")) + elif self.module.params.get("name") is not None: + self.hcloud_firewall = self.client.firewalls.get_by_name(self.module.params.get("name")) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_firewall(self): + self.module.fail_on_missing_params(required_params=["name"]) + params = { + "name": self.module.params.get("name"), + "labels": self.module.params.get("labels"), + } + rules = self.module.params.get("rules") + if rules is not None: + params["rules"] = [ + FirewallRule( + direction=rule["direction"], + protocol=rule["protocol"], + source_ips=rule["source_ips"] if rule["source_ips"] is not None else [], + destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [], + port=rule["port"], + description=rule["description"], + ) + for rule in rules + ] + + if not self.module.check_mode: + try: + self.client.firewalls.create(**params) + except HCloudException as exception: + self.fail_json_hcloud(exception, params=params) + + self._mark_as_changed() + self._get_firewall() + + def _update_firewall(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_firewall.name != name: + self.module.fail_on_missing_params(required_params=["id"]) + if not self.module.check_mode: + self.hcloud_firewall.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_firewall.labels != labels: + if not self.module.check_mode: + self.hcloud_firewall.update(labels=labels) + self._mark_as_changed() + + rules = self.module.params.get("rules") + if rules is not None and rules != [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules]: + if not self.module.check_mode: + new_rules = [ + FirewallRule( + direction=rule["direction"], + protocol=rule["protocol"], + source_ips=rule["source_ips"] if rule["source_ips"] is not None else [], + destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [], + port=rule["port"], + description=rule["description"], + ) + for rule in rules + ] + self.hcloud_firewall.set_rules(new_rules) + self._mark_as_changed() + + self._get_firewall() + + def present_firewall(self): + self._get_firewall() + if self.hcloud_firewall is None: + self._create_firewall() + else: + self._update_firewall() + + def delete_firewall(self): + self._get_firewall() + if self.hcloud_firewall is not None: + if not self.module.check_mode: + if self.hcloud_firewall.applied_to: + if self.module.params.get("force"): + actions = self.hcloud_firewall.remove_from_resources(self.hcloud_firewall.applied_to) + for action in actions: + action.wait_until_finished() + else: + self.module.warn( + f"Firewall {self.hcloud_firewall.name} is currently used by " + "other resources. You need to unassign the resources before " + "deleting the Firewall or use force=true." + ) + + retry_count = 0 + while True: + try: + self.hcloud_firewall.delete() + break + except APIException as exception: + if "is still in use" in exception.message and retry_count < 10: + retry_count += 1 + time.sleep(0.5 * retry_count) + continue + self.fail_json_hcloud(exception) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + self._mark_as_changed() + self.hcloud_firewall = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + labels={"type": "dict"}, + rules=dict( + type="list", + elements="dict", + options=dict( + description={"type": "str"}, + direction={"type": "str", "choices": ["in", "out"]}, + protocol={"type": "str", "choices": ["icmp", "udp", "tcp", "esp", "gre"]}, + port={"type": "str"}, + source_ips={"type": "list", "elements": "str", "default": []}, + destination_ips={"type": "list", "elements": "str", "default": []}, + ), + required_together=[["direction", "protocol"]], + required_if=[ + ["protocol", "udp", ["port"]], + ["protocol", "tcp", ["port"]], + ], + ), + force={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + required_if=[["state", "present", ["name"]]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFirewall.define_module() + + hcloud = AnsibleHCloudFirewall(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_firewall() + elif state == "present": + hcloud.present_firewall() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/firewall_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/firewall_info.py new file mode 100644 index 000000000..7e7a623d0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/firewall_info.py @@ -0,0 +1,246 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: firewall_info +short_description: Gather infos about the Hetzner Cloud Firewalls. + +description: + - Gather facts about your Hetzner Cloud Firewalls. + +author: + - Jonas Lammler (@jooola) + +options: + id: + description: + - The ID of the Firewall you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name for the Firewall you want to get. + type: str + label_selector: + description: + - The label selector for the Firewalls you want to get. + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather hcloud Firewall infos + hetzner.hcloud.firewall_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_firewall_info: + description: List of Firewalls. + returned: always + type: list + elements: dict + contains: + id: + description: Numeric identifier of the firewall. + returned: always + type: int + sample: 1937415 + name: + description: Name of the firewall. + returned: always + type: str + sample: my-firewall + labels: + description: User-defined labels (key-value pairs). + returned: always + type: dict + rules: + description: List of rules the firewall contain. + returned: always + type: list + elements: dict + contains: + description: + description: User defined description of this rule. + type: str + returned: always + sample: allow http from anywhere + direction: + description: The direction of the firewall rule. + type: str + returned: always + sample: in + protocol: + description: The protocol of the firewall rule. + type: str + returned: always + sample: tcp + port: + description: The port or port range allowed by this rule. + type: str + returned: if RV(hcloud_firewall_info[].rules[].protocol=tcp) or RV(hcloud_firewall_info[].rules[].protocol=udp) + sample: "80" + source_ips: + description: List of source CIDRs that are allowed within this rule. + type: list + elements: str + returned: always + sample: ["0.0.0.0/0", "::/0"] + destination_ips: + description: List of destination CIDRs that are allowed within this rule. + type: list + elements: str + returned: always + sample: [] + applied_to: + description: List of Resources the Firewall is applied to. + returned: always + type: list + elements: dict + contains: + type: + description: Type of the resource. + type: str + choices: [server, label_selector] + sample: label_selector + server: + description: ID of the server. + type: int + sample: 12345 + label_selector: + description: Label selector value. + type: str + sample: env=prod + applied_to_resources: + description: List of Resources the Firewall label selector is applied to. + returned: if RV(hcloud_firewall_info[].applied_to[].type=label_selector) + type: list + elements: dict + contains: + type: + description: Type of resource referenced. + type: str + choices: [server] + sample: server + server: + description: ID of the Server. + type: int + sample: 12345 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.firewalls import ( + BoundFirewall, + FirewallResource, + FirewallRule, +) + + +class AnsibleHCloudFirewallInfo(AnsibleHCloud): + represent = "hcloud_firewall_info" + + hcloud_firewall_info: list[BoundFirewall] | None = None + + def _prepare_result(self): + tmp = [] + + for firewall in self.hcloud_firewall_info: + if firewall is None: + continue + + tmp.append( + { + "id": to_native(firewall.id), + "name": to_native(firewall.name), + "labels": firewall.labels, + "rules": [self._prepare_result_rule(rule) for rule in firewall.rules], + "applied_to": [self._prepare_result_applied_to(resource) for resource in firewall.applied_to], + } + ) + + return tmp + + def _prepare_result_rule(self, rule: FirewallRule): + return { + "description": to_native(rule.description) if rule.description is not None else None, + "direction": to_native(rule.direction), + "protocol": to_native(rule.protocol), + "port": to_native(rule.port) if rule.port is not None else None, + "source_ips": [to_native(cidr) for cidr in rule.source_ips], + "destination_ips": [to_native(cidr) for cidr in rule.destination_ips], + } + + def _prepare_result_applied_to(self, resource: FirewallResource): + result = { + "type": to_native(resource.type), + "server": to_native(resource.server.id) if resource.server is not None else None, + "label_selector": ( + to_native(resource.label_selector.selector) if resource.label_selector is not None else None + ), + } + if resource.applied_to_resources is not None: + result["applied_to_resources"] = [ + { + "type": to_native(item.type), + "server": to_native(item.server.id) if item.server is not None else None, + } + for item in resource.applied_to_resources + ] + return result + + def get_firewalls(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_firewall_info = [self.client.firewalls.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_firewall_info = [self.client.firewalls.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_firewall_info = self.client.firewalls.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_firewall_info = self.client.firewalls.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFirewallInfo.define_module() + hcloud = AnsibleHCloudFirewallInfo(module) + + hcloud.get_firewalls() + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/firewall_resource.py b/ansible_collections/hetzner/hcloud/plugins/modules/firewall_resource.py new file mode 100644 index 000000000..207f27092 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/firewall_resource.py @@ -0,0 +1,243 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: firewall_resource +short_description: Manage Resources a Hetzner Cloud Firewall is applied to. + +description: + - Add and Remove Resources a Hetzner Cloud Firewall is applied to. + +author: + - Jonas Lammler (@jooola) + +version_added: 2.5.0 +options: + firewall: + description: + - Name or ID of the Hetzner Cloud Firewall. + type: str + required: true + servers: + description: + - List of Server Name or ID. + type: list + elements: str + label_selectors: + description: + - List of Label Selector. + type: list + elements: str + state: + description: + - State of the firewall resources. + default: present + choices: [absent, present] + type: str + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Apply a firewall to a list of servers + hetzner.hcloud.firewall_resource: + name: my-firewall + servers: + - my-server + - 3456789 + state: present + +- name: Remove a firewall from a list of servers + hetzner.hcloud.firewall_resource: + name: my-firewall + servers: + - my-server + - 3456789 + state: absent + +- name: Apply a firewall to resources using label selectors + hetzner.hcloud.firewall_resource: + name: my-firewall + label_selectors: + - env=prod + state: present + +- name: Remove a firewall from resources using label selectors + hetzner.hcloud.firewall_resource: + name: my-firewall + label_selectors: + - env=prod + state: absent +""" + +RETURN = """ +hcloud_firewall_resource: + description: The Resources a Hetzner Cloud Firewall is applied to. + returned: always + type: dict + contains: + firewall: + description: + - Name of the Hetzner Cloud Firewall. + type: str + sample: my-firewall + servers: + description: + - List of Server Name. + type: list + elements: str + sample: [my-server1, my-server2] + label_selectors: + description: + - List of Label Selector. + type: list + elements: str + sample: [env=prod] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.firewalls import ( + BoundFirewall, + FirewallResource, + FirewallResourceLabelSelector, +) +from ..module_utils.vendor.hcloud.servers import BoundServer + + +class AnsibleHCloudFirewallResource(AnsibleHCloud): + represent = "hcloud_firewall_resource" + + hcloud_firewall_resource: BoundFirewall | None = None + + def _prepare_result(self): + servers = [] + label_selectors = [] + for resource in self.hcloud_firewall_resource.applied_to: + if resource.type == FirewallResource.TYPE_SERVER: + servers.append(to_native(resource.server.name)) + elif resource.type == FirewallResource.TYPE_LABEL_SELECTOR: + label_selectors.append(to_native(resource.label_selector.selector)) + + return { + "firewall": to_native(self.hcloud_firewall_resource.name), + "servers": servers, + "label_selectors": label_selectors, + } + + def _get_firewall(self): + try: + self.hcloud_firewall_resource = self._client_get_by_name_or_id( + "firewalls", + self.module.params.get("firewall"), + ) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _diff_firewall_resources(self, operator) -> list[FirewallResource]: + before = self._prepare_result() + + resources: list[FirewallResource] = [] + + servers: list[str] | None = self.module.params.get("servers") + if servers: + for server_param in servers: + try: + server: BoundServer = self._client_get_by_name_or_id("servers", server_param) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + if operator(server.name, before["servers"]): + resources.append( + FirewallResource( + type=FirewallResource.TYPE_SERVER, + server=server, + ) + ) + + label_selectors = self.module.params.get("label_selectors") + if label_selectors: + for label_selector in label_selectors: + if operator(label_selector, before["label_selectors"]): + resources.append( + FirewallResource( + type=FirewallResource.TYPE_LABEL_SELECTOR, + label_selector=FirewallResourceLabelSelector(selector=label_selector), + ) + ) + + return resources + + def present_firewall_resources(self): + self._get_firewall() + resources = self._diff_firewall_resources( + lambda to_add, before: to_add not in before, + ) + if resources: + if not self.module.check_mode: + actions = self.hcloud_firewall_resource.apply_to_resources(resources=resources) + for action in actions: + action.wait_until_finished() + + self.hcloud_firewall_resource.reload() + + self._mark_as_changed() + + def absent_firewall_resources(self): + self._get_firewall() + resources = self._diff_firewall_resources( + lambda to_remove, before: to_remove in before, + ) + if resources: + if not self.module.check_mode: + actions = self.hcloud_firewall_resource.remove_from_resources(resources=resources) + for action in actions: + action.wait_until_finished() + + self.hcloud_firewall_resource.reload() + + self._mark_as_changed() + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec={ + "firewall": {"type": "str", "required": True}, + "servers": {"type": "list", "elements": "str"}, + "label_selectors": {"type": "list", "elements": "str"}, + "state": { + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + }, + required_one_of=[["servers", "label_selectors"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFirewallResource.define_module() + + hcloud = AnsibleHCloudFirewallResource(module) + state = module.params.get("state") + if state == "absent": + hcloud.absent_firewall_resources() + elif state == "present": + hcloud.present_firewall_resources() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/floating_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/floating_ip.py new file mode 100644 index 000000000..e037dd7a1 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/floating_ip.py @@ -0,0 +1,340 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: floating_ip + +short_description: Create and manage cloud Floating IPs on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Floating IPs on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 0.1.0 +options: + id: + description: + - The ID of the Hetzner Cloud Floating IPs to manage. + - Only required if no Floating IP I(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Floating IPs to manage. + - Only required if no Floating IP I(id) is given or a Floating IP does not exist. + type: str + description: + description: + - The Description of the Hetzner Cloud Floating IPs. + type: str + home_location: + description: + - Home Location of the Hetzner Cloud Floating IP. + - Required if no I(server) is given and Floating IP does not exist. + type: str + server: + description: + - Server Name the Floating IP should be assigned to. + - Required if no I(home_location) is given and Floating IP does not exist. + type: str + type: + description: + - Type of the Floating IP. + - Required if Floating IP does not exist + choices: [ ipv4, ipv6 ] + type: str + force: + description: + - Force the assignment or deletion of the Floating IP. + type: bool + delete_protection: + description: + - Protect the Floating IP for deletion. + type: bool + labels: + description: + - User-defined labels (key-value pairs). + type: dict + state: + description: + - State of the Floating IP. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic IPv4 Floating IP + hetzner.hcloud.floating_ip: + name: my-floating-ip + home_location: fsn1 + type: ipv4 + state: present +- name: Create a basic IPv6 Floating IP + hetzner.hcloud.floating_ip: + name: my-floating-ip + home_location: fsn1 + type: ipv6 + state: present +- name: Assign a Floating IP to a server + hetzner.hcloud.floating_ip: + name: my-floating-ip + server: 1234 + state: present +- name: Assign a Floating IP to another server + hetzner.hcloud.floating_ip: + name: my-floating-ip + server: 1234 + force: true + state: present +- name: Floating IP should be absent + hetzner.hcloud.floating_ip: + name: my-floating-ip + state: absent +""" + +RETURN = """ +hcloud_floating_ip: + description: The Floating IP instance + returned: Always + type: complex + contains: + id: + description: ID of the Floating IP + type: int + returned: Always + sample: 12345 + name: + description: Name of the Floating IP + type: str + returned: Always + sample: my-floating-ip + description: + description: Description of the Floating IP + type: str + returned: Always + sample: my-floating-ip + ip: + description: IP Address of the Floating IP + type: str + returned: Always + sample: 116.203.104.109 + type: + description: Type of the Floating IP + type: str + returned: Always + sample: ipv4 + home_location: + description: Name of the home location of the Floating IP + type: str + returned: Always + sample: fsn1 + server: + description: Name of the server the Floating IP is assigned to. + type: str + returned: Always + sample: "my-server" + delete_protection: + description: True if Floating IP is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.floating_ips import BoundFloatingIP + + +class AnsibleHCloudFloatingIP(AnsibleHCloud): + represent = "hcloud_floating_ip" + + hcloud_floating_ip: BoundFloatingIP | None = None + + def _prepare_result(self): + server = None + + if self.hcloud_floating_ip.server is not None: + server = to_native(self.hcloud_floating_ip.server.name) + return { + "id": to_native(self.hcloud_floating_ip.id), + "name": to_native(self.hcloud_floating_ip.name), + "description": to_native(self.hcloud_floating_ip.description), + "ip": to_native(self.hcloud_floating_ip.ip), + "type": to_native(self.hcloud_floating_ip.type), + "home_location": to_native(self.hcloud_floating_ip.home_location.name), + "labels": self.hcloud_floating_ip.labels, + "server": server, + "delete_protection": self.hcloud_floating_ip.protection["delete"], + } + + def _get_floating_ip(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_floating_ip = self.client.floating_ips.get_by_id(self.module.params.get("id")) + else: + self.hcloud_floating_ip = self.client.floating_ips.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_floating_ip(self): + self.module.fail_on_missing_params(required_params=["type"]) + try: + params = { + "description": self.module.params.get("description"), + "type": self.module.params.get("type"), + "name": self.module.params.get("name"), + } + if self.module.params.get("home_location") is not None: + params["home_location"] = self.client.locations.get_by_name(self.module.params.get("home_location")) + elif self.module.params.get("server") is not None: + params["server"] = self.client.servers.get_by_name(self.module.params.get("server")) + else: + self.module.fail_json(msg="one of the following is required: home_location, server") + + if self.module.params.get("labels") is not None: + params["labels"] = self.module.params.get("labels") + if not self.module.check_mode: + resp = self.client.floating_ips.create(**params) + self.hcloud_floating_ip = resp.floating_ip + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_floating_ip() + + def _update_floating_ip(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_floating_ip.labels: + if not self.module.check_mode: + self.hcloud_floating_ip.update(labels=labels) + self._mark_as_changed() + + description = self.module.params.get("description") + if description is not None and description != self.hcloud_floating_ip.description: + if not self.module.check_mode: + self.hcloud_floating_ip.update(description=description) + self._mark_as_changed() + + server = self.module.params.get("server") + if server is not None and self.hcloud_floating_ip.server is not None: + if self.module.params.get("force") and server != self.hcloud_floating_ip.server.name: + if not self.module.check_mode: + self.hcloud_floating_ip.assign(self.client.servers.get_by_name(server)) + self._mark_as_changed() + elif server != self.hcloud_floating_ip.server.name: + self.module.warn( + "Floating IP is already assigned to another server " + f"{self.hcloud_floating_ip.server.name}. You need to " + "unassign the Floating IP or use force=true." + ) + self._mark_as_changed() + elif server is not None and self.hcloud_floating_ip.server is None: + if not self.module.check_mode: + self.hcloud_floating_ip.assign(self.client.servers.get_by_name(server)) + self._mark_as_changed() + elif server is None and self.hcloud_floating_ip.server is not None: + if not self.module.check_mode: + self.hcloud_floating_ip.unassign() + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_floating_ip.protection["delete"]: + if not self.module.check_mode: + self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_floating_ip() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def present_floating_ip(self): + self._get_floating_ip() + if self.hcloud_floating_ip is None: + self._create_floating_ip() + else: + self._update_floating_ip() + + def delete_floating_ip(self): + try: + self._get_floating_ip() + if self.hcloud_floating_ip is not None: + if self.module.params.get("force") or self.hcloud_floating_ip.server is None: + if not self.module.check_mode: + self.client.floating_ips.delete(self.hcloud_floating_ip) + else: + self.module.warn( + "Floating IP is currently assigned to server " + f"{self.hcloud_floating_ip.server.name}. You need to " + "unassign the Floating IP or use force=true." + ) + self._mark_as_changed() + self.hcloud_floating_ip = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + description={"type": "str"}, + server={"type": "str"}, + home_location={"type": "str"}, + force={"type": "bool"}, + type={"choices": ["ipv4", "ipv6"]}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + mutually_exclusive=[["home_location", "server"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFloatingIP.define_module() + + hcloud = AnsibleHCloudFloatingIP(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_floating_ip() + elif state == "present": + hcloud.present_floating_ip() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/floating_ip_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/floating_ip_info.py new file mode 100644 index 000000000..663d29622 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/floating_ip_info.py @@ -0,0 +1,180 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: floating_ip_info + +short_description: Gather infos about the Hetzner Cloud Floating IPs. + +description: + - Gather facts about your Hetzner Cloud Floating IPs. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Floating IP you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name for the Floating IP you want to get. + type: str + label_selector: + description: + - The label selector for the Floating IP you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud Floating ip infos + hetzner.hcloud.floating_ip_info: + register: output +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_floating_ip_info: + description: The Floating ip infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Floating IP + returned: always + type: int + sample: 1937415 + name: + description: Name of the Floating IP + returned: Always + type: str + sample: my-floating-ip + version_added: "0.1.0" + description: + description: Description of the Floating IP + returned: always + type: str + sample: Falkenstein DC 8 + ip: + description: IP address of the Floating IP + returned: always + type: str + sample: 131.232.99.1 + type: + description: Type of the Floating IP + returned: always + type: str + sample: ipv4 + server: + description: Name of the server where the Floating IP is assigned to. + returned: always + type: str + sample: my-server + home_location: + description: Location the Floating IP was created in + returned: always + type: str + sample: fsn1 + delete_protection: + description: True if the Floating IP is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.floating_ips import BoundFloatingIP + + +class AnsibleHCloudFloatingIPInfo(AnsibleHCloud): + represent = "hcloud_floating_ip_info" + + hcloud_floating_ip_info: list[BoundFloatingIP] | None = None + + def _prepare_result(self): + tmp = [] + + for floating_ip in self.hcloud_floating_ip_info: + if floating_ip is not None: + server_name = None + if floating_ip.server is not None: + server_name = floating_ip.server.name + tmp.append( + { + "id": to_native(floating_ip.id), + "name": to_native(floating_ip.name), + "description": to_native(floating_ip.description), + "ip": to_native(floating_ip.ip), + "type": to_native(floating_ip.type), + "server": to_native(server_name), + "home_location": to_native(floating_ip.home_location.name), + "labels": floating_ip.labels, + "delete_protection": floating_ip.protection["delete"], + } + ) + + return tmp + + def get_floating_ips(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_floating_ip_info = self.client.floating_ips.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_floating_ip_info = self.client.floating_ips.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudFloatingIPInfo.define_module() + hcloud = AnsibleHCloudFloatingIPInfo(module) + + hcloud.get_floating_ips() + result = hcloud.get_result() + + ansible_info = {"hcloud_floating_ip_info": result["hcloud_floating_ip_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py deleted file mode 100644 index 0f6dcf0f2..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/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_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 exist. - 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 exist. - type: str - private_key: - description: - - Certificate key in PEM format. - - Required if certificate does not exist. - type: str - domain_names: - description: - - Certificate key in PEM format. - - Required if certificate does not exist. - type: list - default: [ ] - elements: str - type: - description: - - Choose between uploading a Certificate in PEM format or requesting a managed Let's Encrypt Certificate. - default: uploaded - choices: [ uploaded, managed ] - 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 - - -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), - "type": to_native(self.hcloud_certificate.type), - "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 Exception as e: - self.module.fail_json(msg=e.message) - - def _create_certificate(self): - self.module.fail_on_missing_params( - required_params=["name"] - ) - - params = { - "name": self.module.params.get("name"), - "labels": self.module.params.get("labels") - } - if self.module.params.get('type') == 'uploaded': - self.module.fail_on_missing_params( - required_params=["certificate", "private_key"] - ) - params["certificate"] = self.module.params.get("certificate") - params["private_key"] = self.module.params.get("private_key") - if not self.module.check_mode: - try: - self.client.certificates.create(**params) - except Exception as e: - self.module.fail_json(msg=e.message) - else: - self.module.fail_on_missing_params( - required_params=["domain_names"] - ) - params["domain_names"] = self.module.params.get("domain_names") - if not self.module.check_mode: - try: - resp = self.client.certificates.create_managed(**params) - resp.action.wait_until_finished(max_retries=1000) - except Exception as e: - self.module.fail_json(msg=e.message) - - self._mark_as_changed() - self._get_certificate() - - def _update_certificate(self): - try: - 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() - except Exception as e: - self.module.fail_json(msg=e.message) - 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: - try: - self.client.certificates.delete(self.hcloud_certificate) - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_certificate = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - type={ - "choices": ["uploaded", "managed"], - "default": "uploaded", - }, - domain_names={"type": "list", "elements": "str", "default": []}, - certificate={"type": "str"}, - private_key={"type": "str", "no_log": True}, - 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py deleted file mode 100644 index 855706f1f..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/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_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 - - -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 Exception 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py deleted file mode 100644 index 8cebabf8c..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/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_datacenter_info - -short_description: Gather info about the Hetzner Cloud datacenters. - -description: - - Gather info about your Hetzner Cloud datacenters. - - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). - Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the datacenter you want to get. - type: int - name: - description: - - The name of the datacenter you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud datacenter info - hcloud_datacenter_info: - register: output -- name: Print the gathered info - debug: - var: output -""" - -RETURN = """ -hcloud_datacenter_info: - description: - - The datacenter info as list - - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). - Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! - returned: always - type: complex - contains: - id: - description: Numeric identifier of the datacenter - returned: always - type: int - sample: 1937415 - name: - description: Name of the datacenter - returned: always - type: str - sample: fsn1-dc8 - description: - description: Detail description of the datacenter - returned: always - type: str - sample: Falkenstein DC 8 - location: - description: Name of the location where the datacenter resides in - returned: always - type: str - sample: fsn1 - city: - description: City of the location - returned: always - type: str - sample: fsn1 -""" - -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 - - -class AnsibleHcloudDatacenterInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_datacenter_info") - self.hcloud_datacenter_info = None - - def _prepare_result(self): - tmp = [] - - for datacenter in self.hcloud_datacenter_info: - if datacenter is not None: - tmp.append({ - "id": to_native(datacenter.id), - "name": to_native(datacenter.name), - "description": to_native(datacenter.description), - "location": to_native(datacenter.location.name) - }) - - return tmp - - def get_datacenters(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_datacenter_info = [self.client.datacenters.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_datacenter_info = [self.client.datacenters.get_by_name( - self.module.params.get("name") - )] - else: - self.hcloud_datacenter_info = self.client.datacenters.get_all() - - except Exception 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 = AnsibleHcloudDatacenterInfo.define_module() - - is_old_facts = module._name == 'hcloud_datacenter_facts' - if is_old_facts: - module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - hcloud = AnsibleHcloudDatacenterInfo(module) - - hcloud.get_datacenters() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_datacenter_facts': result['hcloud_datacenter_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_datacenter_info': result['hcloud_datacenter_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py deleted file mode 100644 index 8cebabf8c..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/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_datacenter_info - -short_description: Gather info about the Hetzner Cloud datacenters. - -description: - - Gather info about your Hetzner Cloud datacenters. - - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). - Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the datacenter you want to get. - type: int - name: - description: - - The name of the datacenter you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud datacenter info - hcloud_datacenter_info: - register: output -- name: Print the gathered info - debug: - var: output -""" - -RETURN = """ -hcloud_datacenter_info: - description: - - The datacenter info as list - - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). - Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! - returned: always - type: complex - contains: - id: - description: Numeric identifier of the datacenter - returned: always - type: int - sample: 1937415 - name: - description: Name of the datacenter - returned: always - type: str - sample: fsn1-dc8 - description: - description: Detail description of the datacenter - returned: always - type: str - sample: Falkenstein DC 8 - location: - description: Name of the location where the datacenter resides in - returned: always - type: str - sample: fsn1 - city: - description: City of the location - returned: always - type: str - sample: fsn1 -""" - -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 - - -class AnsibleHcloudDatacenterInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_datacenter_info") - self.hcloud_datacenter_info = None - - def _prepare_result(self): - tmp = [] - - for datacenter in self.hcloud_datacenter_info: - if datacenter is not None: - tmp.append({ - "id": to_native(datacenter.id), - "name": to_native(datacenter.name), - "description": to_native(datacenter.description), - "location": to_native(datacenter.location.name) - }) - - return tmp - - def get_datacenters(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_datacenter_info = [self.client.datacenters.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_datacenter_info = [self.client.datacenters.get_by_name( - self.module.params.get("name") - )] - else: - self.hcloud_datacenter_info = self.client.datacenters.get_all() - - except Exception 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 = AnsibleHcloudDatacenterInfo.define_module() - - is_old_facts = module._name == 'hcloud_datacenter_facts' - if is_old_facts: - module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - hcloud = AnsibleHcloudDatacenterInfo(module) - - hcloud.get_datacenters() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_datacenter_facts': result['hcloud_datacenter_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_datacenter_info': result['hcloud_datacenter_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py deleted file mode 100644 index 34608977e..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py +++ /dev/null @@ -1,359 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2020, Hetzner Cloud GmbH -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = ''' ---- -module: hcloud_firewall - -short_description: Create and manage firewalls on the Hetzner Cloud. - - -description: - - Create, update and manage firewalls on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@lkaemmerling) - -options: - id: - description: - - The ID of the Hetzner Cloud firewall to manage. - - Only required if no firewall I(name) is given - type: int - name: - description: - - The Name of the Hetzner Cloud firewall to manage. - - Only required if no firewall I(id) is given, or a firewall does not exist. - type: str - labels: - description: - - User-defined labels (key-value pairs) - type: dict - rules: - description: - - List of rules the firewall should contain. - type: list - elements: dict - suboptions: - direction: - description: - - The direction of the firewall rule. - type: str - choices: [ in, out ] - port: - description: - - The port of the firewall rule. - type: str - protocol: - description: - - The protocol of the firewall rule. - type: str - choices: [ icmp, tcp, udp, esp, gre ] - source_ips: - description: - - List of CIDRs that are allowed within this rule - type: list - elements: str - default: [ ] - destination_ips: - description: - - List of CIDRs that are allowed within this rule - type: list - elements: str - default: [ ] - description: - description: - - User defined description of this rule. - type: str - state: - description: - - State of the firewall. - default: present - choices: [ absent, present ] - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud -''' - -EXAMPLES = """ -- name: Create a basic firewall - hcloud_firewall: - name: my-firewall - state: present - -- name: Create a firewall with rules - hcloud_firewall: - name: my-firewall - rules: - - direction: in - protocol: icmp - source_ips: - - 0.0.0.0/0 - - ::/0 - description: allow icmp in - state: present - -- name: Create a firewall with labels - hcloud_firewall: - name: my-firewall - labels: - key: value - mylabel: 123 - state: present - -- name: Ensure the firewall is absent (remove if needed) - hcloud_firewall: - name: my-firewall - state: absent -""" - -RETURN = """ -hcloud_firewall: - description: The firewall instance - returned: Always - type: complex - contains: - id: - description: Numeric identifier of the firewall - returned: always - type: int - sample: 1937415 - name: - description: Name of the firewall - returned: always - type: str - sample: my firewall - rules: - description: List of Rules within this Firewall - returned: always - type: complex - contains: - direction: - description: Direction of the Firewall Rule - type: str - returned: always - sample: in - protocol: - description: Protocol of the Firewall Rule - type: str - returned: always - sample: icmp - port: - description: Port of the Firewall Rule, None/Null if protocol is icmp - type: str - returned: always - sample: in - source_ips: - description: Source IPs of the Firewall - type: list - elements: str - returned: always - destination_ips: - description: Source IPs of the Firewall - type: list - elements: str - returned: always - description: - description: User defined description of the Firewall Rule - type: str - returned: always - labels: - description: User-defined labels (key-value pairs) - returned: always - type: dict -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native -from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud -import time - -try: - from hcloud.firewalls.domain import FirewallRule - from hcloud import APIException -except ImportError: - APIException = None - FirewallRule = None - - -class AnsibleHcloudFirewall(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_firewall") - self.hcloud_firewall = None - - def _prepare_result(self): - return { - "id": to_native(self.hcloud_firewall.id), - "name": to_native(self.hcloud_firewall.name), - "rules": [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules], - "labels": self.hcloud_firewall.labels - } - - def _prepare_result_rule(self, rule): - return { - "direction": rule.direction, - "protocol": to_native(rule.protocol), - "port": to_native(rule.port) if rule.port is not None else None, - "source_ips": [to_native(cidr) for cidr in rule.source_ips], - "destination_ips": [to_native(cidr) for cidr in rule.destination_ips], - "description": to_native(rule.description) if rule.description is not None else None, - } - - def _get_firewall(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_firewall = self.client.firewalls.get_by_id( - self.module.params.get("id") - ) - elif self.module.params.get("name") is not None: - self.hcloud_firewall = self.client.firewalls.get_by_name( - self.module.params.get("name") - ) - - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_firewall(self): - self.module.fail_on_missing_params( - required_params=["name"] - ) - params = { - "name": self.module.params.get("name"), - "labels": self.module.params.get("labels") - } - rules = self.module.params.get("rules") - if rules is not None: - params["rules"] = [ - FirewallRule( - direction=rule["direction"], - protocol=rule["protocol"], - source_ips=rule["source_ips"] if rule["source_ips"] is not None else [], - destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [], - port=rule["port"], - description=rule["description"], - ) - for rule in rules - ] - if not self.module.check_mode: - try: - self.client.firewalls.create(**params) - except Exception as e: - self.module.fail_json(msg=e.message, **params) - self._mark_as_changed() - self._get_firewall() - - def _update_firewall(self): - name = self.module.params.get("name") - if name is not None and self.hcloud_firewall.name != name: - self.module.fail_on_missing_params( - required_params=["id"] - ) - if not self.module.check_mode: - self.hcloud_firewall.update(name=name) - self._mark_as_changed() - - labels = self.module.params.get("labels") - if labels is not None and self.hcloud_firewall.labels != labels: - if not self.module.check_mode: - self.hcloud_firewall.update(labels=labels) - self._mark_as_changed() - - rules = self.module.params.get("rules") - if rules is not None and rules != [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules]: - if not self.module.check_mode: - new_rules = [ - FirewallRule( - direction=rule["direction"], - protocol=rule["protocol"], - source_ips=rule["source_ips"] if rule["source_ips"] is not None else [], - destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [], - port=rule["port"], - description=rule["description"], - ) - for rule in rules - ] - self.hcloud_firewall.set_rules(new_rules) - self._mark_as_changed() - self._get_firewall() - - def present_firewall(self): - self._get_firewall() - if self.hcloud_firewall is None: - self._create_firewall() - else: - self._update_firewall() - - def delete_firewall(self): - self._get_firewall() - if self.hcloud_firewall is not None: - if not self.module.check_mode: - retry_count = 0 - while retry_count < 10: - try: - self.client.firewalls.delete(self.hcloud_firewall) - break - except APIException as e: - if "is still in use" in e.message: - retry_count = retry_count + 1 - time.sleep(0.5 * retry_count) - else: - self.module.fail_json(msg=e.message) - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_firewall = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - rules=dict( - type="list", - elements="dict", - options=dict( - direction={"type": "str", "choices": ["in", "out"]}, - protocol={"type": "str", "choices": ["icmp", "udp", "tcp", "esp", "gre"]}, - port={"type": "str"}, - source_ips={"type": "list", "elements": "str", "default": []}, - destination_ips={"type": "list", "elements": "str", "default": []}, - description={"type": "str"}, - ), - required_together=[["direction", "protocol"]], - ), - labels={"type": "dict"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name']], - required_if=[['state', 'present', ['name']]], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudFirewall.define_module() - - hcloud = AnsibleHcloudFirewall(module) - state = module.params.get("state") - if state == "absent": - hcloud.delete_firewall() - elif state == "present": - hcloud.present_firewall() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py deleted file mode 100644 index 1ee61ea13..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/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_floating_ip - -short_description: Create and manage cloud Floating IPs on the Hetzner Cloud. - - -description: - - Create, update and manage cloud Floating IPs on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@lkaemmerling) -version_added: 0.1.0 -options: - id: - description: - - The ID of the Hetzner Cloud Floating IPs to manage. - - Only required if no Floating IP I(name) is given. - type: int - name: - description: - - The Name of the Hetzner Cloud Floating IPs to manage. - - Only required if no Floating IP I(id) is given or a Floating IP does not exist. - type: str - description: - description: - - The Description of the Hetzner Cloud Floating IPs. - type: str - home_location: - description: - - Home Location of the Hetzner Cloud Floating IP. - - Required if no I(server) is given and Floating IP does not exist. - type: str - server: - description: - - Server Name the Floating IP should be assigned to. - - Required if no I(home_location) is given and Floating IP does not exist. - type: str - type: - description: - - Type of the Floating IP. - - Required if Floating IP does not exist - choices: [ ipv4, ipv6 ] - type: str - force: - description: - - Force the assignment or deletion of the Floating IP. - type: bool - delete_protection: - description: - - Protect the Floating IP for deletion. - type: bool - labels: - description: - - User-defined labels (key-value pairs). - type: dict - state: - description: - - State of the Floating IP. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.6.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic IPv4 Floating IP - hcloud_floating_ip: - name: my-floating-ip - home_location: fsn1 - type: ipv4 - state: present -- name: Create a basic IPv6 Floating IP - hcloud_floating_ip: - name: my-floating-ip - home_location: fsn1 - type: ipv6 - state: present -- name: Assign a Floating IP to a server - hcloud_floating_ip: - name: my-floating-ip - server: 1234 - state: present -- name: Assign a Floating IP to another server - hcloud_floating_ip: - name: my-floating-ip - server: 1234 - force: yes - state: present -- name: Floating IP should be absent - hcloud_floating_ip: - name: my-floating-ip - state: absent -""" - -RETURN = """ -hcloud_floating_ip: - description: The Floating IP instance - returned: Always - type: complex - contains: - id: - description: ID of the Floating IP - type: int - returned: Always - sample: 12345 - name: - description: Name of the Floating IP - type: str - returned: Always - sample: my-floating-ip - description: - description: Description of the Floating IP - type: str - returned: Always - sample: my-floating-ip - ip: - description: IP Address of the Floating IP - type: str - returned: Always - sample: 116.203.104.109 - type: - description: Type of the Floating IP - type: str - returned: Always - sample: ipv4 - home_location: - description: Name of the home location of the Floating IP - type: str - returned: Always - sample: fsn1 - server: - description: Name of the server the Floating IP is assigned to. - type: str - returned: Always - sample: "my-server" - delete_protection: - description: True if Floating IP is protected for deletion - type: bool - returned: always - sample: false - version_added: "0.1.0" - labels: - description: User-defined labels (key-value pairs) - type: dict - returned: Always - sample: - key: value - mylabel: 123 -""" - -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 - - -class AnsibleHcloudFloatingIP(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_floating_ip") - self.hcloud_floating_ip = None - - def _prepare_result(self): - server = None - - if self.hcloud_floating_ip.server is not None: - server = to_native(self.hcloud_floating_ip.server.name) - return { - "id": to_native(self.hcloud_floating_ip.id), - "name": to_native(self.hcloud_floating_ip.name), - "description": to_native(self.hcloud_floating_ip.description), - "ip": to_native(self.hcloud_floating_ip.ip), - "type": to_native(self.hcloud_floating_ip.type), - "home_location": to_native(self.hcloud_floating_ip.home_location.name), - "labels": self.hcloud_floating_ip.labels, - "server": server, - "delete_protection": self.hcloud_floating_ip.protection["delete"], - } - - def _get_floating_ip(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_floating_ip = self.client.floating_ips.get_by_id( - self.module.params.get("id") - ) - else: - self.hcloud_floating_ip = self.client.floating_ips.get_by_name( - self.module.params.get("name") - ) - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_floating_ip(self): - self.module.fail_on_missing_params( - required_params=["type"] - ) - try: - params = { - "description": self.module.params.get("description"), - "type": self.module.params.get("type"), - "name": self.module.params.get("name"), - } - if self.module.params.get("home_location") is not None: - params["home_location"] = self.client.locations.get_by_name( - self.module.params.get("home_location") - ) - elif self.module.params.get("server") is not None: - params["server"] = self.client.servers.get_by_name( - self.module.params.get("server") - ) - else: - self.module.fail_json(msg="one of the following is required: home_location, server") - - if self.module.params.get("labels") is not None: - params["labels"] = self.module.params.get("labels") - if not self.module.check_mode: - resp = self.client.floating_ips.create(**params) - self.hcloud_floating_ip = resp.floating_ip - - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None: - self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_floating_ip() - - def _update_floating_ip(self): - try: - labels = self.module.params.get("labels") - if labels is not None and labels != self.hcloud_floating_ip.labels: - if not self.module.check_mode: - self.hcloud_floating_ip.update(labels=labels) - self._mark_as_changed() - - description = self.module.params.get("description") - if description is not None and description != self.hcloud_floating_ip.description: - if not self.module.check_mode: - self.hcloud_floating_ip.update(description=description) - self._mark_as_changed() - - server = self.module.params.get("server") - if server is not None and self.hcloud_floating_ip.server is not None: - if self.module.params.get("force") and server != self.hcloud_floating_ip.server.name: - if not self.module.check_mode: - self.hcloud_floating_ip.assign( - self.client.servers.get_by_name(server) - ) - self._mark_as_changed() - elif server != self.hcloud_floating_ip.server.name: - self.module.warn( - "Floating IP is already assigned to another server %s. You need to unassign the Floating IP or use force=yes." - % self.hcloud_floating_ip.server.name - ) - self._mark_as_changed() - elif server is not None and self.hcloud_floating_ip.server is None: - if not self.module.check_mode: - self.hcloud_floating_ip.assign( - self.client.servers.get_by_name(server) - ) - self._mark_as_changed() - elif server is None and self.hcloud_floating_ip.server is not None: - if not self.module.check_mode: - self.hcloud_floating_ip.unassign() - self._mark_as_changed() - - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None and delete_protection != self.hcloud_floating_ip.protection["delete"]: - if not self.module.check_mode: - self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished() - self._mark_as_changed() - - self._get_floating_ip() - except Exception as e: - self.module.fail_json(msg=e.message) - - def present_floating_ip(self): - self._get_floating_ip() - if self.hcloud_floating_ip is None: - self._create_floating_ip() - else: - self._update_floating_ip() - - def delete_floating_ip(self): - try: - self._get_floating_ip() - if self.hcloud_floating_ip is not None: - if self.module.params.get("force") or self.hcloud_floating_ip.server is None: - if not self.module.check_mode: - self.client.floating_ips.delete(self.hcloud_floating_ip) - else: - self.module.warn( - "Floating IP is currently assigned to server %s. You need to unassign the Floating IP or use force=yes." - % self.hcloud_floating_ip.server.name - ) - self._mark_as_changed() - self.hcloud_floating_ip = None - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - description={"type": "str"}, - server={"type": "str"}, - home_location={"type": "str"}, - force={"type": "bool"}, - type={"choices": ["ipv4", "ipv6"]}, - labels={"type": "dict"}, - delete_protection={"type": "bool"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name']], - mutually_exclusive=[['home_location', 'server']], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudFloatingIP.define_module() - - hcloud = AnsibleHcloudFloatingIP(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_floating_ip() - elif state == "present": - hcloud.present_floating_ip() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py deleted file mode 100644 index 2ec359600..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/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_floating_ip_info - -short_description: Gather infos about the Hetzner Cloud Floating IPs. - -description: - - Gather facts about your Hetzner Cloud Floating IPs. - - This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts). - Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Floating IP you want to get. - type: int - label_selector: - description: - - The label selector for the Floating IP you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud Floating ip infos - hcloud_floating_ip_info: - register: output -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_floating_ip_info: - description: The Floating ip infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the Floating IP - returned: always - type: int - sample: 1937415 - name: - description: Name of the Floating IP - returned: Always - type: str - sample: my-floating-ip - version_added: "0.1.0" - description: - description: Description of the Floating IP - returned: always - type: str - sample: Falkenstein DC 8 - ip: - description: IP address of the Floating IP - returned: always - type: str - sample: 131.232.99.1 - type: - description: Type of the Floating IP - returned: always - type: str - sample: ipv4 - server: - description: Name of the server where the Floating IP is assigned to. - returned: always - type: str - sample: my-server - home_location: - description: Location the Floating IP was created in - returned: always - type: str - sample: fsn1 - delete_protection: - description: True if the Floating IP is protected for deletion - returned: always - type: bool - version_added: "0.1.0" - 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 - - -class AnsibleHcloudFloatingIPInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_floating_ip_info") - self.hcloud_floating_ip_info = None - - def _prepare_result(self): - tmp = [] - - for floating_ip in self.hcloud_floating_ip_info: - if floating_ip is not None: - server_name = None - if floating_ip.server is not None: - server_name = floating_ip.server.name - tmp.append({ - "id": to_native(floating_ip.id), - "name": to_native(floating_ip.name), - "description": to_native(floating_ip.description), - "ip": to_native(floating_ip.ip), - "type": to_native(floating_ip.type), - "server": to_native(server_name), - "home_location": to_native(floating_ip.home_location.name), - "labels": floating_ip.labels, - "delete_protection": floating_ip.protection["delete"], - }) - - return tmp - - def get_floating_ips(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_floating_ip_info = self.client.floating_ips.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_floating_ip_info = self.client.floating_ips.get_all() - - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - label_selector={"type": "str"}, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudFloatingIPInfo.define_module() - - is_old_facts = module._name == 'hcloud_floating_ip_facts' - if is_old_facts: - module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudFloatingIPInfo(module) - - hcloud.get_floating_ips() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_floating_ip_facts': result['hcloud_floating_ip_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_floating_ip_info': result['hcloud_floating_ip_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py deleted file mode 100644 index 2ec359600..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/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_floating_ip_info - -short_description: Gather infos about the Hetzner Cloud Floating IPs. - -description: - - Gather facts about your Hetzner Cloud Floating IPs. - - This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts). - Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Floating IP you want to get. - type: int - label_selector: - description: - - The label selector for the Floating IP you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud Floating ip infos - hcloud_floating_ip_info: - register: output -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_floating_ip_info: - description: The Floating ip infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the Floating IP - returned: always - type: int - sample: 1937415 - name: - description: Name of the Floating IP - returned: Always - type: str - sample: my-floating-ip - version_added: "0.1.0" - description: - description: Description of the Floating IP - returned: always - type: str - sample: Falkenstein DC 8 - ip: - description: IP address of the Floating IP - returned: always - type: str - sample: 131.232.99.1 - type: - description: Type of the Floating IP - returned: always - type: str - sample: ipv4 - server: - description: Name of the server where the Floating IP is assigned to. - returned: always - type: str - sample: my-server - home_location: - description: Location the Floating IP was created in - returned: always - type: str - sample: fsn1 - delete_protection: - description: True if the Floating IP is protected for deletion - returned: always - type: bool - version_added: "0.1.0" - 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 - - -class AnsibleHcloudFloatingIPInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_floating_ip_info") - self.hcloud_floating_ip_info = None - - def _prepare_result(self): - tmp = [] - - for floating_ip in self.hcloud_floating_ip_info: - if floating_ip is not None: - server_name = None - if floating_ip.server is not None: - server_name = floating_ip.server.name - tmp.append({ - "id": to_native(floating_ip.id), - "name": to_native(floating_ip.name), - "description": to_native(floating_ip.description), - "ip": to_native(floating_ip.ip), - "type": to_native(floating_ip.type), - "server": to_native(server_name), - "home_location": to_native(floating_ip.home_location.name), - "labels": floating_ip.labels, - "delete_protection": floating_ip.protection["delete"], - }) - - return tmp - - def get_floating_ips(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_floating_ip_info = self.client.floating_ips.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_floating_ip_info = self.client.floating_ips.get_all() - - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - label_selector={"type": "str"}, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudFloatingIPInfo.define_module() - - is_old_facts = module._name == 'hcloud_floating_ip_facts' - if is_old_facts: - module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudFloatingIPInfo(module) - - hcloud.get_floating_ips() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_floating_ip_facts': result['hcloud_floating_ip_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_floating_ip_info': result['hcloud_floating_ip_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py deleted file mode 100644 index 8acd8846a..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/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_image_info - -short_description: Gather infos about your Hetzner Cloud images. - - -description: - - Gather infos about your Hetzner Cloud images. - - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). - Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the image you want to get. - type: int - name: - description: - - The name of the image you want to get. - type: str - label_selector: - description: - - The label selector for the images you want to get. - type: str - type: - description: - - The type for the images you want to get. - default: system - choices: [ system, snapshot, backup ] - type: str - architecture: - description: - - The architecture for the images you want to get. - type: str - choices: [ x86, arm ] -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud image infos - hcloud_image_info: - register: output - -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_image_info: - description: The image infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the image - returned: always - type: int - sample: 1937415 - type: - description: Type of the image - returned: always - type: str - sample: system - status: - description: Status of the image - returned: always - type: str - sample: available - name: - description: Name of the image - returned: always - type: str - sample: ubuntu-18.04 - description: - description: Detail description of the image - returned: always - type: str - sample: Ubuntu 18.04 Standard 64 bit - os_flavor: - description: OS flavor of the image - returned: always - type: str - sample: ubuntu - os_version: - description: OS version of the image - returned: always - type: str - sample: 18.04 - architecture: - description: Image is compatible with this architecture - returned: always - type: str - sample: x86 - 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 - - -class AnsibleHcloudImageInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_image_info") - self.hcloud_image_info = None - - def _prepare_result(self): - tmp = [] - - for image in self.hcloud_image_info: - if image is not None: - tmp.append({ - "id": to_native(image.id), - "status": to_native(image.status), - "type": to_native(image.type), - "name": to_native(image.name), - "description": to_native(image.description), - "os_flavor": to_native(image.os_flavor), - "os_version": to_native(image.os_version), - "architecture": to_native(image.architecture), - "labels": image.labels, - }) - return tmp - - def get_images(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_image_info = [self.client.images.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None: - self.hcloud_image_info = [self.client.images.get_by_name_and_architecture( - self.module.params.get("name"), - self.module.params.get("architecture") - )] - elif self.module.params.get("name") is not None: - self.hcloud_image_info = [self.client.images.get_by_name( - self.module.params.get("name") - )] - else: - params = {} - label_selector = self.module.params.get("label_selector") - if label_selector: - params["label_selector"] = label_selector - - image_type = self.module.params.get("type") - if image_type: - params["type"] = image_type - - architecture = self.module.params.get("architecture") - if architecture: - params["architecture"] = architecture - - self.hcloud_image_info = self.client.images.get_all(**params) - - except Exception 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"}, - type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"}, - architecture={"choices": ["x86", "arm"], "type": "str"}, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudImageInfo.define_module() - - is_old_facts = module._name == 'hcloud_image_facts' - if is_old_facts: - module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudImageInfo(module) - hcloud.get_images() - result = hcloud.get_result() - - if is_old_facts: - ansible_info = { - 'hcloud_imagen_facts': result['hcloud_image_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_image_info': result['hcloud_image_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py deleted file mode 100644 index 8acd8846a..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/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_image_info - -short_description: Gather infos about your Hetzner Cloud images. - - -description: - - Gather infos about your Hetzner Cloud images. - - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). - Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the image you want to get. - type: int - name: - description: - - The name of the image you want to get. - type: str - label_selector: - description: - - The label selector for the images you want to get. - type: str - type: - description: - - The type for the images you want to get. - default: system - choices: [ system, snapshot, backup ] - type: str - architecture: - description: - - The architecture for the images you want to get. - type: str - choices: [ x86, arm ] -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud image infos - hcloud_image_info: - register: output - -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_image_info: - description: The image infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the image - returned: always - type: int - sample: 1937415 - type: - description: Type of the image - returned: always - type: str - sample: system - status: - description: Status of the image - returned: always - type: str - sample: available - name: - description: Name of the image - returned: always - type: str - sample: ubuntu-18.04 - description: - description: Detail description of the image - returned: always - type: str - sample: Ubuntu 18.04 Standard 64 bit - os_flavor: - description: OS flavor of the image - returned: always - type: str - sample: ubuntu - os_version: - description: OS version of the image - returned: always - type: str - sample: 18.04 - architecture: - description: Image is compatible with this architecture - returned: always - type: str - sample: x86 - 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 - - -class AnsibleHcloudImageInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_image_info") - self.hcloud_image_info = None - - def _prepare_result(self): - tmp = [] - - for image in self.hcloud_image_info: - if image is not None: - tmp.append({ - "id": to_native(image.id), - "status": to_native(image.status), - "type": to_native(image.type), - "name": to_native(image.name), - "description": to_native(image.description), - "os_flavor": to_native(image.os_flavor), - "os_version": to_native(image.os_version), - "architecture": to_native(image.architecture), - "labels": image.labels, - }) - return tmp - - def get_images(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_image_info = [self.client.images.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None: - self.hcloud_image_info = [self.client.images.get_by_name_and_architecture( - self.module.params.get("name"), - self.module.params.get("architecture") - )] - elif self.module.params.get("name") is not None: - self.hcloud_image_info = [self.client.images.get_by_name( - self.module.params.get("name") - )] - else: - params = {} - label_selector = self.module.params.get("label_selector") - if label_selector: - params["label_selector"] = label_selector - - image_type = self.module.params.get("type") - if image_type: - params["type"] = image_type - - architecture = self.module.params.get("architecture") - if architecture: - params["architecture"] = architecture - - self.hcloud_image_info = self.client.images.get_all(**params) - - except Exception 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"}, - type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"}, - architecture={"choices": ["x86", "arm"], "type": "str"}, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudImageInfo.define_module() - - is_old_facts = module._name == 'hcloud_image_facts' - if is_old_facts: - module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudImageInfo(module) - hcloud.get_images() - result = hcloud.get_result() - - if is_old_facts: - ansible_info = { - 'hcloud_imagen_facts': result['hcloud_image_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_image_info': result['hcloud_image_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py deleted file mode 100644 index 9c6c2bbaf..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/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 exist. - type: str - load_balancer_type: - description: - - The Load Balancer Type of the Hetzner Cloud Load Balancer to manage. - - Required if Load Balancer does not exist. - type: str - location: - description: - - Location of Load Balancer. - - Required if no I(network_zone) is given and Load Balancer does not exist. - type: str - network_zone: - description: - - Network Zone of Load Balancer. - - Required of no I(location) is given and Load Balancer does not exist. - 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 - - -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": False if self.hcloud_load_balancer.public_net.enabled else True, - } - - 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 Exception 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"] - ) - try: - 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) - - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None: - self._get_load_balancer() - self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_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 != (not 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() - - load_balancer_type = self.module.params.get("load_balancer_type") - if load_balancer_type is not None and self.hcloud_load_balancer.load_balancer_type.name != load_balancer_type: - new_load_balancer_type = self.client.load_balancer_types.get_by_name(load_balancer_type) - if not new_load_balancer_type: - self.module.fail_json(msg="unknown load balancer type") - if not self.module.check_mode: - self.hcloud_load_balancer.change_type( - load_balancer_type=new_load_balancer_type, - ).wait_until_finished(max_retries=1000) - - self._mark_as_changed() - self._get_load_balancer() - except Exception 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 Exception 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py deleted file mode 100644 index 159dad258..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py +++ /dev/null @@ -1,398 +0,0 @@ -#!/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_info - -short_description: Gather infos about your Hetzner Cloud Load Balancers. - - -description: - - Gather infos about your Hetzner Cloud Load Balancers.. - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Load Balancers you want to get. - type: int - name: - description: - - The name of the Load Balancers you want to get. - type: str - label_selector: - description: - - The label selector for the Load Balancers you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud load_balancer infos - hcloud_load_balancer_info: - register: output - -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_load_balancer_info: - description: The load_balancer infos as list - 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 - targets: - description: The targets of the Load Balancer - 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 - label_selector: - description: Label Selector - type: str - returned: if I(type) is label_selector - sample: application=backend - ip: - description: IP of the dedicated server - type: str - returned: if I(type) is ip - sample: 127.0.0.1 - 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 - sample: true - returned: always - services: - description: all services from this Load Balancer - returned: Always - type: complex - contains: - 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 - - -class AnsibleHcloudLoadBalancerInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_load_balancer_info") - self.hcloud_load_balancer_info = None - - def _prepare_result(self): - tmp = [] - - for load_balancer in self.hcloud_load_balancer_info: - if load_balancer is not None: - services = [self._prepare_service_result(service) for service in load_balancer.services] - targets = [self._prepare_target_result(target) for target in load_balancer.targets] - - private_ipv4_address = None if len(load_balancer.private_net) == 0 else to_native( - load_balancer.private_net[0].ip) - tmp.append({ - "id": to_native(load_balancer.id), - "name": to_native(load_balancer.name), - "ipv4_address": to_native(load_balancer.public_net.ipv4.ip), - "ipv6_address": to_native(load_balancer.public_net.ipv6.ip), - "private_ipv4_address": private_ipv4_address, - "load_balancer_type": to_native(load_balancer.load_balancer_type.name), - "location": to_native(load_balancer.location.name), - "labels": load_balancer.labels, - "delete_protection": load_balancer.protection["delete"], - "disable_public_interface": False if load_balancer.public_net.enabled else True, - "targets": targets, - "services": services - }) - return tmp - - @staticmethod - def _prepare_service_result(service): - http = None - if service.protocol != "tcp": - http = { - "cookie_name": to_native(service.http.cookie_name), - "cookie_lifetime": service.http.cookie_name, - "redirect_http": service.http.redirect_http, - "sticky_sessions": service.http.sticky_sessions, - "certificates": [to_native(certificate.name) for certificate in - service.http.certificates], - } - health_check = { - "protocol": to_native(service.health_check.protocol), - "port": service.health_check.port, - "interval": service.health_check.interval, - "timeout": service.health_check.timeout, - "retries": service.health_check.retries, - } - if service.health_check.protocol != "tcp": - health_check["http"] = { - "domain": to_native(service.health_check.http.domain), - "path": to_native(service.health_check.http.path), - "response": to_native(service.health_check.http.response), - "certificates": [to_native(status_code) for status_code in - service.health_check.http.status_codes], - "tls": service.health_check.http.tls, - } - return { - "protocol": to_native(service.protocol), - "listen_port": service.listen_port, - "destination_port": service.destination_port, - "proxyprotocol": service.proxyprotocol, - "http": http, - "health_check": health_check, - } - - @staticmethod - def _prepare_target_result(target): - result = { - "type": to_native(target.type), - "use_private_ip": target.use_private_ip - } - if target.type == "server": - result["server"] = to_native(target.server.name) - elif target.type == "label_selector": - result["label_selector"] = to_native(target.label_selector.selector) - elif target.type == "ip": - result["ip"] = to_native(target.ip.ip) - return result - - def get_load_balancers(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_name( - self.module.params.get("name") - )] - else: - params = {} - label_selector = self.module.params.get("label_selector") - if label_selector: - params["label_selector"] = label_selector - - self.hcloud_load_balancer_info = self.client.load_balancers.get_all(**params) - - except Exception 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 = AnsibleHcloudLoadBalancerInfo.define_module() - - hcloud = AnsibleHcloudLoadBalancerInfo(module) - hcloud.get_load_balancers() - result = hcloud.get_result() - - ansible_info = { - 'hcloud_load_balancer_info': result['hcloud_load_balancer_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py deleted file mode 100644 index 63a7c5471..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/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 - - -class AnsibleHcloudLoadBalancerNetwork(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, 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: - network = self.module.params.get("network") - self.hcloud_network = self.client.networks.get_by_name(network) - if not self.hcloud_network: - self.module.fail_json(msg="Network does not exist: %s" % network) - - load_balancer_name = self.module.params.get("load_balancer") - self.hcloud_load_balancer = self.client.load_balancers.get_by_name( - load_balancer_name - ) - if not self.hcloud_load_balancer: - self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name) - - self.hcloud_load_balancer_network = None - except Exception 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 Exception 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: - try: - self.hcloud_load_balancer.detach_from_network( - self.hcloud_load_balancer_network.network).wait_until_finished() - self._mark_as_changed() - except Exception as e: - self.module.fail_json(msg=e.message) - - 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py deleted file mode 100644 index b5edcc6b5..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py +++ /dev/null @@ -1,620 +0,0 @@ -#!/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 exist and protocol is tcp. - type: int - protocol: - description: - - Protocol of the service. - - Required if Load Balancer does not exist. - type: str - choices: [ http, https, tcp ] - proxyprotocol: - description: - - Enable the PROXY protocol. - type: bool - default: False - 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 - default: False - redirect_http: - description: - - Redirect Traffic from Port 80 to Port 443, only available if protocol is https - type: bool - default: False - 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 - default: False - 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 LoadBalancerService, LoadBalancerServiceHttp, \ - LoadBalancerHealthCheck, LoadBalancerHealtCheckHttp - from hcloud import APIException -except ImportError: - APIException = None - - -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.http.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: - load_balancer_name = self.module.params.get("load_balancer") - self.hcloud_load_balancer = self.client.load_balancers.get_by_name( - load_balancer_name - ) - if not self.hcloud_load_balancer: - self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name) - - self._get_load_balancer_service() - except Exception 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 Exception 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 Exception: - hcloud_cert = self.client.certificates.get_by_id( - certificate - ) - except Exception 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 = LoadBalancerHealthCheck() - 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 = LoadBalancerHealtCheckHttp() - 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 Exception 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: - try: - self.hcloud_load_balancer.delete_service(self.hcloud_load_balancer_service).wait_until_finished( - max_retries=1000) - except Exception as e: - self.module.fail_json(msg=e.message) - 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py deleted file mode 100644 index 760884466..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py +++ /dev/null @@ -1,321 +0,0 @@ -#!/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, label_selector, ip ] - 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 - label_selector: - description: - - A Label Selector that will be used to determine the targets dynamically - - Required if I(type) is label_selector - type: str - ip: - description: - - An IP from a Hetzner Dedicated Server, needs to belongs to the same user as the project. - - Required if I(type) is ip - 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 - default: False - 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 - hetzner.hcloud.hcloud_load_balancer_target: - type: server - load_balancer: my-LoadBalancer - server: my-server - state: present - -- name: Create a label_selector Load Balancer target - hetzner.hcloud.hcloud_load_balancer_target: - type: label_selector - load_balancer: my-LoadBalancer - label_selector: application=backend - state: present - -- name: Create an IP Load Balancer target - hetzner.hcloud.hcloud_load_balancer_target: - type: ip - load_balancer: my-LoadBalancer - ip: 127.0.0.1 - state: present - -- name: Ensure the Load Balancer target is absent (remove if needed) - hetzner.hcloud.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 - label_selector: - description: Label Selector - type: str - returned: if I(type) is label_selector - sample: application=backend - ip: - description: IP of the dedicated server - type: str - returned: if I(type) is ip - sample: 127.0.0.1 - 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 - sample: true - returned: always -""" - -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 LoadBalancerTarget, LoadBalancerTargetLabelSelector, LoadBalancerTargetIP -except ImportError: - LoadBalancerTarget = None - LoadBalancerTargetLabelSelector = None - LoadBalancerTargetIP = None - - -class AnsibleHcloudLoadBalancerTarget(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_load_balancer_target") - self.hcloud_load_balancer = None - self.hcloud_load_balancer_target = None - self.hcloud_server = None - - def _prepare_result(self): - result = { - "type": to_native(self.hcloud_load_balancer_target.type), - "load_balancer": to_native(self.hcloud_load_balancer.name), - "use_private_ip": self.hcloud_load_balancer_target.use_private_ip - } - - if self.hcloud_load_balancer_target.type == "server": - result["server"] = to_native(self.hcloud_load_balancer_target.server.name) - elif self.hcloud_load_balancer_target.type == "label_selector": - result["label_selector"] = to_native(self.hcloud_load_balancer_target.label_selector.selector) - elif self.hcloud_load_balancer_target.type == "ip": - result["ip"] = to_native(self.hcloud_load_balancer_target.ip.ip) - return result - - def _get_load_balancer_and_target(self): - try: - load_balancer_name = self.module.params.get("load_balancer") - self.hcloud_load_balancer = self.client.load_balancers.get_by_name( - load_balancer_name - ) - if not self.hcloud_load_balancer: - self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name) - - if self.module.params.get("type") == "server": - server_name = self.module.params.get("server") - self.hcloud_server = self.client.servers.get_by_name(server_name) - if not self.hcloud_server: - self.module.fail_json(msg="Server not found: %s" % server_name) - - self.hcloud_load_balancer_target = None - except Exception 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" and target.type == "server": - if target.server.id == self.hcloud_server.id: - self.hcloud_load_balancer_target = target - elif self.module.params.get("type") == "label_selector" and target.type == "label_selector": - if target.label_selector.selector == self.module.params.get("label_selector"): - self.hcloud_load_balancer_target = target - elif self.module.params.get("type") == "ip" and target.type == "ip": - if target.ip.ip == self.module.params.get("ip"): - 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")) - elif self.module.params.get("type") == "label_selector": - self.module.fail_on_missing_params( - required_params=["label_selector"] - ) - params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), - label_selector=LoadBalancerTargetLabelSelector( - selector=self.module.params.get("label_selector")), - use_private_ip=self.module.params.get("use_private_ip")) - elif self.module.params.get("type") == "ip": - self.module.fail_on_missing_params( - required_params=["ip"] - ) - params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), - ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")), - use_private_ip=False) - - if not self.module.check_mode: - try: - self.hcloud_load_balancer.add_target(**params).wait_until_finished() - except Exception as e: - if e.code == "locked" or e.code == "conflict": - self._create_load_balancer_target() - else: - 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": - self.module.fail_on_missing_params( - required_params=["server"] - ) - target = LoadBalancerTarget(type=self.module.params.get("type"), - server=self.hcloud_server) - elif self.module.params.get("type") == "label_selector": - self.module.fail_on_missing_params( - required_params=["label_selector"] - ) - target = LoadBalancerTarget(type=self.module.params.get("type"), - label_selector=LoadBalancerTargetLabelSelector( - selector=self.module.params.get("label_selector")), - use_private_ip=self.module.params.get("use_private_ip")) - elif self.module.params.get("type") == "ip": - self.module.fail_on_missing_params( - required_params=["ip"] - ) - target = LoadBalancerTarget(type=self.module.params.get("type"), - ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")), - use_private_ip=False) - try: - self.hcloud_load_balancer.remove_target(target).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - 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", "label_selector", "ip"]}, - load_balancer={"type": "str", "required": True}, - server={"type": "str"}, - label_selector={"type": "str"}, - ip={"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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py deleted file mode 100644 index a481ea9c9..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/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 - - -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 Exception 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py deleted file mode 100644 index 623c6ab68..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/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_location_info - -short_description: Gather infos about your Hetzner Cloud locations. - - -description: - - Gather infos about your Hetzner Cloud locations. - - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). - Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the location you want to get. - type: int - name: - description: - - The name of the location you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud location infos - hcloud_location_info: - register: output - -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_location_info: - description: The location infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the location - returned: always - type: int - sample: 1937415 - name: - description: Name of the location - returned: always - type: str - sample: fsn1 - description: - description: Detail description of the location - returned: always - type: str - sample: Falkenstein DC Park 1 - country: - description: Country code of the location - returned: always - type: str - sample: DE - city: - description: City of the location - returned: always - type: str - sample: Falkenstein -""" - -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 - - -class AnsibleHcloudLocationInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_location_info") - self.hcloud_location_info = None - - def _prepare_result(self): - tmp = [] - - for location in self.hcloud_location_info: - if location is not None: - tmp.append({ - "id": to_native(location.id), - "name": to_native(location.name), - "description": to_native(location.description), - "city": to_native(location.city), - "country": to_native(location.country) - }) - return tmp - - def get_locations(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_location_info = [self.client.locations.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_location_info = [self.client.locations.get_by_name( - self.module.params.get("name") - )] - else: - self.hcloud_location_info = self.client.locations.get_all() - - except Exception 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 = AnsibleHcloudLocationInfo.define_module() - - is_old_facts = module._name == 'hcloud_location_facts' - if is_old_facts: - module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudLocationInfo(module) - hcloud.get_locations() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_location_facts': result['hcloud_location_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_location_info': result['hcloud_location_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py deleted file mode 100644 index 623c6ab68..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/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_location_info - -short_description: Gather infos about your Hetzner Cloud locations. - - -description: - - Gather infos about your Hetzner Cloud locations. - - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). - Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the location you want to get. - type: int - name: - description: - - The name of the location you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud location infos - hcloud_location_info: - register: output - -- name: Print the gathered infos - debug: - var: output -""" - -RETURN = """ -hcloud_location_info: - description: The location infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the location - returned: always - type: int - sample: 1937415 - name: - description: Name of the location - returned: always - type: str - sample: fsn1 - description: - description: Detail description of the location - returned: always - type: str - sample: Falkenstein DC Park 1 - country: - description: Country code of the location - returned: always - type: str - sample: DE - city: - description: City of the location - returned: always - type: str - sample: Falkenstein -""" - -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 - - -class AnsibleHcloudLocationInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_location_info") - self.hcloud_location_info = None - - def _prepare_result(self): - tmp = [] - - for location in self.hcloud_location_info: - if location is not None: - tmp.append({ - "id": to_native(location.id), - "name": to_native(location.name), - "description": to_native(location.description), - "city": to_native(location.city), - "country": to_native(location.country) - }) - return tmp - - def get_locations(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_location_info = [self.client.locations.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_location_info = [self.client.locations.get_by_name( - self.module.params.get("name") - )] - else: - self.hcloud_location_info = self.client.locations.get_all() - - except Exception 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 = AnsibleHcloudLocationInfo.define_module() - - is_old_facts = module._name == 'hcloud_location_facts' - if is_old_facts: - module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudLocationInfo(module) - hcloud.get_locations() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_location_facts': result['hcloud_location_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_location_info': result['hcloud_location_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py deleted file mode 100644 index 9c005d29f..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/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_network - -short_description: Create and manage cloud Networks on the Hetzner Cloud. - - -description: - - Create, update and manage cloud Networks on the Hetzner Cloud. - - You need at least hcloud-python 1.3.0. - -author: - - Lukas Kaemmerling (@lkaemmerling) - -options: - id: - description: - - The ID of the Hetzner Cloud Networks to manage. - - Only required if no Network I(name) is given. - type: int - name: - description: - - The Name of the Hetzner Cloud Network to manage. - - Only required if no Network I(id) is given or a Network does not exist. - type: str - ip_range: - description: - - IP range of the Network. - - Required if Network does not exist. - type: str - labels: - description: - - User-defined labels (key-value pairs). - type: dict - delete_protection: - description: - - Protect the Network for deletion. - type: bool - state: - description: - - State of the Network. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.3.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic network - hcloud_network: - name: my-network - ip_range: 10.0.0.0/8 - state: present - -- name: Ensure the Network is absent (remove if needed) - hcloud_network: - name: my-network - state: absent -""" - -RETURN = """ -hcloud_network: - description: The Network - returned: always - type: complex - contains: - id: - description: ID of the Network - type: int - returned: always - sample: 12345 - name: - description: Name of the Network - type: str - returned: always - sample: my-volume - ip_range: - description: IP range of the Network - type: str - returned: always - sample: 10.0.0.0/8 - delete_protection: - description: True if Network is protected for deletion - type: bool - returned: always - sample: false - version_added: "0.1.0" - labels: - description: User-defined labels (key-value pairs) - type: dict - returned: always - sample: - key: value - mylabel: 123 -""" - -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 - - -class AnsibleHcloudNetwork(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_network") - self.hcloud_network = None - - def _prepare_result(self): - return { - "id": to_native(self.hcloud_network.id), - "name": to_native(self.hcloud_network.name), - "ip_range": to_native(self.hcloud_network.ip_range), - "delete_protection": self.hcloud_network.protection["delete"], - "labels": self.hcloud_network.labels, - } - - def _get_network(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_network = self.client.networks.get_by_id( - self.module.params.get("id") - ) - else: - self.hcloud_network = self.client.networks.get_by_name( - self.module.params.get("name") - ) - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_network(self): - - self.module.fail_on_missing_params( - required_params=["name", "ip_range"] - ) - params = { - "name": self.module.params.get("name"), - "ip_range": self.module.params.get("ip_range"), - "labels": self.module.params.get("labels"), - } - try: - if not self.module.check_mode: - self.client.networks.create(**params) - - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None: - self._get_network() - self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_network() - - def _update_network(self): - try: - labels = self.module.params.get("labels") - if labels is not None and labels != self.hcloud_network.labels: - if not self.module.check_mode: - self.hcloud_network.update(labels=labels) - self._mark_as_changed() - - ip_range = self.module.params.get("ip_range") - if ip_range is not None and ip_range != self.hcloud_network.ip_range: - if not self.module.check_mode: - self.hcloud_network.change_ip_range(ip_range=ip_range).wait_until_finished() - self._mark_as_changed() - - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None and delete_protection != self.hcloud_network.protection["delete"]: - if not self.module.check_mode: - self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished() - self._mark_as_changed() - except Exception as e: - self.module.fail_json(msg=e.message) - self._get_network() - - def present_network(self): - self._get_network() - if self.hcloud_network is None: - self._create_network() - else: - self._update_network() - - def delete_network(self): - try: - self._get_network() - if self.hcloud_network is not None: - if not self.module.check_mode: - self.client.networks.delete(self.hcloud_network) - self._mark_as_changed() - except Exception as e: - self.module.fail_json(msg=e.message) - self.hcloud_network = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - ip_range={"type": "str"}, - labels={"type": "dict"}, - delete_protection={"type": "bool"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name']], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudNetwork.define_module() - - hcloud = AnsibleHcloudNetwork(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_network() - elif state == "present": - hcloud.present_network() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py deleted file mode 100644 index 382e447aa..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/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_network_info - -short_description: Gather info about your Hetzner Cloud networks. - - -description: - - Gather info about your Hetzner Cloud networks. - -author: - - Christopher Schmitt (@cschmitt-hcloud) - -options: - id: - description: - - The ID of the network you want to get. - type: int - name: - description: - - The name of the network you want to get. - type: str - label_selector: - description: - - The label selector for the network you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud network info - local_action: - module: hcloud_network_info - -- name: Print the gathered info - debug: - var: hcloud_network_info -""" - -RETURN = """ -hcloud_network_info: - description: The network info as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the network - returned: always - type: int - sample: 1937415 - name: - description: Name of the network - returned: always - type: str - sample: awesome-network - ip_range: - description: IP range of the network - returned: always - type: str - sample: 10.0.0.0/16 - subnetworks: - description: Subnetworks belonging to the network - returned: always - type: complex - contains: - type: - description: Type of the subnetwork. - returned: always - type: str - sample: cloud - network_zone: - description: Network of the subnetwork. - returned: always - type: str - sample: eu-central - ip_range: - description: IP range of the subnetwork - returned: always - type: str - sample: 10.0.0.0/24 - gateway: - description: Gateway of this subnetwork - returned: always - type: str - sample: 10.0.0.1 - routes: - description: Routes belonging to the network - returned: always - type: complex - contains: - ip_range: - description: Destination network or host of this route. - returned: always - type: str - sample: 10.0.0.0/16 - gateway: - description: Gateway of this route - returned: always - type: str - sample: 10.0.0.1 - servers: - description: Servers attached to the network - returned: always - type: complex - contains: - id: - description: Numeric identifier of the server - returned: always - type: int - sample: 1937415 - name: - description: Name of the server - returned: always - type: str - sample: my-server - status: - description: Status of the server - returned: always - type: str - sample: running - server_type: - description: Name of the server type of the server - returned: always - type: str - sample: cx11 - ipv4_address: - description: Public IPv4 address of the server, None if not existing - returned: always - type: str - sample: 116.203.104.109 - ipv6: - description: IPv6 network of the server, None if not existing - returned: always - type: str - sample: 2a01:4f8:1c1c:c140::/64 - location: - description: Name of the location of the server - returned: always - type: str - sample: fsn1 - datacenter: - description: Name of the datacenter of the server - returned: always - type: str - sample: fsn1-dc14 - rescue_enabled: - description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot - returned: always - type: bool - sample: false - backup_window: - description: Time window (UTC) in which the backup will run, or null if the backups are not enabled - returned: always - type: bool - sample: 22-02 - labels: - description: User-defined labels (key-value pairs) - returned: always - type: dict - delete_protection: - description: True if the network is protected for deletion - returned: always - type: bool - version_added: "0.1.0" - labels: - description: Labels of the network - 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 - - -class AnsibleHcloudNetworkInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_network_info") - self.hcloud_network_info = None - - def _prepare_result(self): - tmp = [] - - for network in self.hcloud_network_info: - if network is not None: - subnets = [] - for subnet in network.subnets: - prepared_subnet = { - "type": subnet.type, - "ip_range": subnet.ip_range, - "network_zone": subnet.network_zone, - "gateway": subnet.gateway, - } - subnets.append(prepared_subnet) - routes = [] - for route in network.routes: - prepared_route = { - "destination": route.destination, - "gateway": route.gateway - } - routes.append(prepared_route) - - servers = [] - for server in network.servers: - image = None if server.image is None else to_native(server.image.name) - ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) - ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) - prepared_server = { - "id": to_native(server.id), - "name": to_native(server.name), - "ipv4_address": ipv4_address, - "ipv6": ipv6, - "image": image, - "server_type": to_native(server.server_type.name), - "datacenter": to_native(server.datacenter.name), - "location": to_native(server.datacenter.location.name), - "rescue_enabled": server.rescue_enabled, - "backup_window": to_native(server.backup_window), - "labels": server.labels, - "status": to_native(server.status), - } - servers.append(prepared_server) - - tmp.append({ - "id": to_native(network.id), - "name": to_native(network.name), - "ip_range": to_native(network.ip_range), - "subnetworks": subnets, - "routes": routes, - "servers": servers, - "labels": network.labels, - "delete_protection": network.protection["delete"], - }) - return tmp - - def get_networks(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_network_info = [self.client.networks.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_network_info = [self.client.networks.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_network_info = self.client.networks.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_network_info = self.client.networks.get_all() - - except Exception 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 = AnsibleHcloudNetworkInfo.define_module() - - hcloud = AnsibleHcloudNetworkInfo(module) - hcloud.get_networks() - result = hcloud.get_result() - info = { - 'hcloud_network_info': result['hcloud_network_info'] - } - module.exit_json(**info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py deleted file mode 100644 index 522bb679d..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/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_placement_group - -short_description: Create and manage placement groups on the Hetzner Cloud. - - -description: - - Create, update and manage placement groups on the Hetzner Cloud. - -author: - - Adrian Huber (@Adi146) - -options: - id: - description: - - The ID of the Hetzner Cloud placement group to manage. - - Only required if no placement group I(name) is given - type: int - name: - description: - - The Name of the Hetzner Cloud placement group to manage. - - Only required if no placement group I(id) is given, or a placement group does not exist. - type: str - labels: - description: - - User-defined labels (key-value pairs) - type: dict - type: - description: - - The Type of the Hetzner Cloud placement group. - type: str - state: - description: - - State of the placement group. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.15.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud -""" - -EXAMPLES = """ -- name: Create a basic placement group - hcloud_placement_group: - name: my-placement-group - state: present - type: spread - -- name: Create a placement group with labels - hcloud_placement_group: - name: my-placement-group - type: spread - labels: - key: value - mylabel: 123 - state: present - -- name: Ensure the placement group is absent (remove if needed) - hcloud_placement_group: - name: my-placement-group - state: absent -""" - -RETURN = """ -hcloud_placement_group: - description: The placement group instance - returned: Always - type: complex - contains: - id: - description: Numeric identifier of the placement group - returned: always - type: int - sample: 1937415 - name: - description: Name of the placement group - returned: always - type: str - sample: my placement group - labels: - description: User-defined labels (key-value pairs) - returned: always - type: dict - type: - description: Type of the placement group - returned: always - type: str - sample: spread - servers: - description: Server IDs of the placement group - returned: always - type: list - elements: int - sample: - - 4711 - - 4712 -""" - -from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native - - -class AnsibleHcloudPlacementGroup(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_placement_group") - self.hcloud_placement_group = None - - def _prepare_result(self): - return { - "id": to_native(self.hcloud_placement_group.id), - "name": to_native(self.hcloud_placement_group.name), - "labels": self.hcloud_placement_group.labels, - "type": to_native(self.hcloud_placement_group.type), - "servers": self.hcloud_placement_group.servers, - } - - def _get_placement_group(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_placement_group = self.client.placement_groups.get_by_id( - self.module.params.get("id") - ) - elif self.module.params.get("name") is not None: - self.hcloud_placement_group = self.client.placement_groups.get_by_name( - self.module.params.get("name") - ) - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_placement_group(self): - self.module.fail_on_missing_params( - required_params=["name"] - ) - params = { - "name": self.module.params.get("name"), - "type": self.module.params.get("type"), - "labels": self.module.params.get("labels"), - } - if not self.module.check_mode: - try: - self.client.placement_groups.create(**params) - except Exception as e: - self.module.fail_json(msg=e.message, **params) - self._mark_as_changed() - self._get_placement_group() - - def _update_placement_group(self): - name = self.module.params.get("name") - if name is not None and self.hcloud_placement_group.name != name: - self.module.fail_on_missing_params( - required_params=["id"] - ) - if not self.module.check_mode: - self.hcloud_placement_group.update(name=name) - self._mark_as_changed() - - labels = self.module.params.get("labels") - if labels is not None and self.hcloud_placement_group.labels != labels: - if not self.module.check_mode: - self.hcloud_placement_group.update(labels=labels) - self._mark_as_changed() - - self._get_placement_group() - - def present_placement_group(self): - self._get_placement_group() - if self.hcloud_placement_group is None: - self._create_placement_group() - else: - self._update_placement_group() - - def delete_placement_group(self): - self._get_placement_group() - if self.hcloud_placement_group is not None: - if not self.module.check_mode: - self.client.placement_groups.delete(self.hcloud_placement_group) - self._mark_as_changed() - self.hcloud_placement_group = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - labels={"type": "dict"}, - type={"type": "str"}, - 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 = AnsibleHcloudPlacementGroup.define_module() - - hcloud = AnsibleHcloudPlacementGroup(module) - state = module.params.get("state") - if state == "absent": - hcloud.delete_placement_group() - elif state == "present": - hcloud.present_placement_group() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py deleted file mode 100644 index c192d5fec..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2022, 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_primary_ip - -short_description: Create and manage cloud Primary IPs on the Hetzner Cloud. - - -description: - - Create, update and manage cloud Primary IPs on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@lkaemmerling) -version_added: 1.8.0 -options: - id: - description: - - The ID of the Hetzner Cloud Primary IPs to manage. - - Only required if no Primary IP I(name) is given. - type: int - name: - description: - - The Name of the Hetzner Cloud Primary IPs to manage. - - Only required if no Primary IP I(id) is given or a Primary IP does not exist. - type: str - datacenter: - description: - - Home Location of the Hetzner Cloud Primary IP. - - Required if no I(server) is given and Primary IP does not exist. - type: str - type: - description: - - Type of the Primary IP. - - Required if Primary IP does not exist - choices: [ ipv4, ipv6 ] - type: str - auto_delete: - description: - - Delete this Primary IP when the resource it is assigned to is deleted - type: bool - default: no - delete_protection: - description: - - Protect the Primary IP for deletion. - type: bool - labels: - description: - - User-defined labels (key-value pairs). - type: dict - state: - description: - - State of the Primary IP. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.9.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic IPv4 Primary IP - hcloud_primary_ip: - name: my-primary-ip - datacenter: fsn1-dc14 - type: ipv4 - state: present -- name: Create a basic IPv6 Primary IP - hcloud_primary_ip: - name: my-primary-ip - datacenter: fsn1-dc14 - type: ipv6 - state: present -- name: Primary IP should be absent - hcloud_primary_ip: - name: my-primary-ip - state: absent -""" - -RETURN = """ -hcloud_primary_ip: - description: The Primary IP instance - returned: Always - type: complex - contains: - id: - description: ID of the Primary IP - type: int - returned: Always - sample: 12345 - name: - description: Name of the Primary IP - type: str - returned: Always - sample: my-primary-ip - ip: - description: IP Address of the Primary IP - type: str - returned: Always - sample: 116.203.104.109 - type: - description: Type of the Primary IP - type: str - returned: Always - sample: ipv4 - datacenter: - description: Name of the datacenter of the Primary IP - type: str - returned: Always - sample: fsn1-dc14 - delete_protection: - description: True if Primary IP is protected for deletion - type: bool - returned: always - sample: false - labels: - description: User-defined labels (key-value pairs) - type: dict - returned: Always - sample: - key: value - mylabel: 123 -""" - -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 - - -class AnsibleHcloudPrimaryIP(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_primary_ip") - self.hcloud_primary_ip = None - - def _prepare_result(self): - return { - "id": to_native(self.hcloud_primary_ip.id), - "name": to_native(self.hcloud_primary_ip.name), - "ip": to_native(self.hcloud_primary_ip.ip), - "type": to_native(self.hcloud_primary_ip.type), - "datacenter": to_native(self.hcloud_primary_ip.datacenter.name), - "labels": self.hcloud_primary_ip.labels, - "delete_protection": self.hcloud_primary_ip.protection["delete"], - } - - def _get_primary_ip(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_primary_ip = self.client.primary_ips.get_by_id( - self.module.params.get("id") - ) - else: - self.hcloud_primary_ip = self.client.primary_ips.get_by_name( - self.module.params.get("name") - ) - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_primary_ip(self): - self.module.fail_on_missing_params( - required_params=["type", "datacenter"] - ) - try: - params = { - "type": self.module.params.get("type"), - "name": self.module.params.get("name"), - "datacenter": self.client.datacenters.get_by_name( - self.module.params.get("datacenter") - ) - } - - if self.module.params.get("labels") is not None: - params["labels"] = self.module.params.get("labels") - if not self.module.check_mode: - resp = self.client.primary_ips.create(**params) - self.hcloud_primary_ip = resp.primary_ip - - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None: - self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e) - self._mark_as_changed() - self._get_primary_ip() - - def _update_primary_ip(self): - try: - labels = self.module.params.get("labels") - if labels is not None and labels != self.hcloud_primary_ip.labels: - if not self.module.check_mode: - self.hcloud_primary_ip.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_primary_ip.protection["delete"]: - if not self.module.check_mode: - self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished() - self._mark_as_changed() - - self._get_primary_ip() - except Exception as e: - self.module.fail_json(msg=e.message) - - def present_primary_ip(self): - self._get_primary_ip() - if self.hcloud_primary_ip is None: - self._create_primary_ip() - else: - self._update_primary_ip() - - def delete_primary_ip(self): - try: - self._get_primary_ip() - if self.hcloud_primary_ip is not None: - if not self.module.check_mode: - self.client.primary_ips.delete(self.hcloud_primary_ip) - self._mark_as_changed() - self.hcloud_primary_ip = None - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - datacenter={"type": "str"}, - auto_delete={"type": "bool", "default": False}, - type={"choices": ["ipv4", "ipv6"]}, - labels={"type": "dict"}, - delete_protection={"type": "bool"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name']], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudPrimaryIP.define_module() - - hcloud = AnsibleHcloudPrimaryIP(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_primary_ip() - elif state == "present": - hcloud.present_primary_ip() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py deleted file mode 100644 index 9f79fbe70..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py +++ /dev/null @@ -1,360 +0,0 @@ -#!/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_rdns - -short_description: Create and manage reverse DNS entries on the Hetzner Cloud. - - -description: - - Create, update and delete reverse DNS entries on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@lkaemmerling) - -options: - server: - description: - - The name of the Hetzner Cloud server you want to add the reverse DNS entry to. - type: str - floating_ip: - description: - - The name of the Hetzner Cloud Floating IP you want to add the reverse DNS entry to. - type: str - primary_ip: - description: - - The name of the Hetzner Cloud Primary IP you want to add the reverse DNS entry to. - type: str - load_balancer: - description: - - The name of the Hetzner Cloud Load Balancer you want to add the reverse DNS entry to. - type: str - ip_address: - description: - - The IP address that should point to I(dns_ptr). - type: str - required: true - dns_ptr: - description: - - The DNS address the I(ip_address) should resolve to. - - Omit the param to reset the reverse DNS entry to the default value. - type: str - state: - description: - - State of the reverse DNS entry. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.3.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a reverse DNS entry for a server - hcloud_rdns: - server: my-server - ip_address: 123.123.123.123 - dns_ptr: example.com - state: present - -- name: Create a reverse DNS entry for a Floating IP - hcloud_rdns: - floating_ip: my-floating-ip - ip_address: 123.123.123.123 - dns_ptr: example.com - state: present - -- name: Create a reverse DNS entry for a Primary IP - hcloud_rdns: - primary_ip: my-primary-ip - ip_address: 123.123.123.123 - dns_ptr: example.com - state: present - -- name: Create a reverse DNS entry for a Load Balancer - hcloud_rdns: - load_balancer: my-load-balancer - ip_address: 123.123.123.123 - dns_ptr: example.com - state: present - -- name: Ensure the reverse DNS entry is absent (remove if needed) - hcloud_rdns: - server: my-server - ip_address: 123.123.123.123 - dns_ptr: example.com - state: absent -""" - -RETURN = """ -hcloud_rdns: - description: The reverse DNS entry - returned: always - type: complex - contains: - server: - description: Name of the server - type: str - returned: always - sample: my-server - floating_ip: - description: Name of the Floating IP - type: str - returned: always - sample: my-floating-ip - primary_ip: - description: Name of the Primary IP - type: str - returned: always - sample: my-primary-ip - load_balancer: - description: Name of the Load Balancer - type: str - returned: always - sample: my-load-balancer - ip_address: - description: The IP address that point to the DNS ptr - type: str - returned: always - sample: 123.123.123.123 - dns_ptr: - description: The DNS that resolves to the IP - type: str - returned: always - sample: example.com -""" - -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 -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils - - -class AnsibleHcloudReverseDNS(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_rdns") - self.hcloud_resource = None - self.hcloud_rdns = None - - def _prepare_result(self): - result = { - "server": None, - "floating_ip": None, - "load_balancer": None, - "ip_address": to_native(self.hcloud_rdns["ip_address"]), - "dns_ptr": to_native(self.hcloud_rdns["dns_ptr"]), - } - - if self.module.params.get("server"): - result["server"] = to_native(self.hcloud_resource.name) - elif self.module.params.get("floating_ip"): - result["floating_ip"] = to_native(self.hcloud_resource.name) - elif self.module.params.get("load_balancer"): - result["load_balancer"] = to_native(self.hcloud_resource.name) - elif self.module.params.get("primary_ip"): - result["primary_ip"] = to_native(self.hcloud_resource.name) - return result - - def _get_resource(self): - try: - if self.module.params.get("server"): - self.hcloud_resource = self.client.servers.get_by_name( - self.module.params.get("server") - ) - if self.hcloud_resource is None: - self.module.fail_json(msg="The selected server does not exist") - elif self.module.params.get("floating_ip"): - self.hcloud_resource = self.client.floating_ips.get_by_name( - self.module.params.get("floating_ip") - ) - if self.hcloud_resource is None: - self.module.fail_json(msg="The selected Floating IP does not exist") - elif self.module.params.get("primary_ip"): - self.hcloud_resource = self.client.primary_ips.get_by_name( - self.module.params.get("primary_ip") - ) - if self.hcloud_resource is None: - self.module.fail_json(msg="The selected Floating IP does not exist") - elif self.module.params.get("load_balancer"): - self.hcloud_resource = self.client.load_balancers.get_by_name( - self.module.params.get("load_balancer") - ) - if self.hcloud_resource is None: - self.module.fail_json(msg="The selected Load Balancer does not exist") - except Exception as e: - self.module.fail_json(msg=e.message) - - def _get_rdns(self): - ip_address = self.module.params.get("ip_address") - if utils.validate_ip_address(ip_address): - if self.module.params.get("server"): - if self.hcloud_resource.public_net.ipv4.ip == ip_address: - self.hcloud_rdns = { - "ip_address": self.hcloud_resource.public_net.ipv4.ip, - "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr, - } - else: - self.module.fail_json(msg="The selected server does not have this IP address") - elif self.module.params.get("floating_ip"): - if self.hcloud_resource.ip == ip_address: - self.hcloud_rdns = { - "ip_address": self.hcloud_resource.ip, - "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"], - } - else: - self.module.fail_json(msg="The selected Floating IP does not have this IP address") - elif self.module.params.get("primary_ip"): - if self.hcloud_resource.ip == ip_address: - self.hcloud_rdns = { - "ip_address": self.hcloud_resource.ip, - "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"], - } - else: - self.module.fail_json(msg="The selected Primary IP does not have this IP address") - elif self.module.params.get("load_balancer"): - if self.hcloud_resource.public_net.ipv4.ip == ip_address: - self.hcloud_rdns = { - "ip_address": self.hcloud_resource.public_net.ipv4.ip, - "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr, - } - else: - self.module.fail_json(msg="The selected Load Balancer does not have this IP address") - - elif utils.validate_ip_v6_address(ip_address): - if self.module.params.get("server"): - for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr: - if ipv6_address_dns_ptr["ip"] == ip_address: - self.hcloud_rdns = { - "ip_address": ipv6_address_dns_ptr["ip"], - "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], - } - elif self.module.params.get("floating_ip"): - for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr: - if ipv6_address_dns_ptr["ip"] == ip_address: - self.hcloud_rdns = { - "ip_address": ipv6_address_dns_ptr["ip"], - "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], - } - elif self.module.params.get("primary_ip"): - for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr: - if ipv6_address_dns_ptr["ip"] == ip_address: - self.hcloud_rdns = { - "ip_address": ipv6_address_dns_ptr["ip"], - "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], - } - elif self.module.params.get("load_balancer"): - for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr: - if ipv6_address_dns_ptr["ip"] == ip_address: - self.hcloud_rdns = { - "ip_address": ipv6_address_dns_ptr["ip"], - "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], - } - else: - self.module.fail_json(msg="The given IP address is not valid") - - def _create_rdns(self): - self.module.fail_on_missing_params( - required_params=["dns_ptr"] - ) - params = { - "ip": self.module.params.get("ip_address"), - "dns_ptr": self.module.params.get("dns_ptr"), - } - - if not self.module.check_mode: - try: - self.hcloud_resource.change_dns_ptr(**params).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_resource() - self._get_rdns() - - def _update_rdns(self): - dns_ptr = self.module.params.get("dns_ptr") - if dns_ptr != self.hcloud_rdns["dns_ptr"]: - params = { - "ip": self.module.params.get("ip_address"), - "dns_ptr": dns_ptr, - } - - if not self.module.check_mode: - try: - self.hcloud_resource.change_dns_ptr(**params).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_resource() - self._get_rdns() - - def present_rdns(self): - self._get_resource() - self._get_rdns() - if self.hcloud_rdns is None: - self._create_rdns() - else: - self._update_rdns() - - def delete_rdns(self): - self._get_resource() - self._get_rdns() - if self.hcloud_rdns is not None: - if not self.module.check_mode: - try: - self.hcloud_resource.change_dns_ptr(ip=self.hcloud_rdns['ip_address'], dns_ptr=None) - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_rdns = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - server={"type": "str"}, - floating_ip={"type": "str"}, - load_balancer={"type": "str"}, - primary_ip={"type": "str"}, - ip_address={"type": "str", "required": True}, - dns_ptr={"type": "str"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['server', 'floating_ip', 'load_balancer', 'primary_ip']], - mutually_exclusive=[["server", "floating_ip", 'load_balancer', 'primary_ip']], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudReverseDNS.define_module() - - hcloud = AnsibleHcloudReverseDNS(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_rdns() - elif state == "present": - hcloud.present_rdns() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py deleted file mode 100644 index c75177953..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/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_route - -short_description: Create and delete cloud routes on the Hetzner Cloud. - - -description: - - Create, update and delete cloud routes on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@lkaemmerling) - -options: - network: - description: - - The name of the Hetzner Cloud Network. - type: str - required: true - destination: - description: - - Destination network or host of this route. - type: str - required: true - gateway: - description: - - Gateway for the route. - type: str - required: true - state: - description: - - State of the route. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.3.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic route - hcloud_route: - network: my-network - destination: 10.100.1.0/24 - gateway: 10.0.1.1 - state: present - -- name: Ensure the route is absent - hcloud_route: - network: my-network - destination: 10.100.1.0/24 - gateway: 10.0.1.1 - state: absent -""" - -RETURN = """ -hcloud_route: - description: One Route of a Network - returned: always - type: complex - contains: - network: - description: Name of the Network - type: str - returned: always - sample: my-network - destination: - description: Destination network or host of this route - type: str - returned: always - sample: 10.0.0.0/8 - gateway: - description: Gateway of the route - type: str - returned: always - sample: 10.0.0.1 -""" - -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.networks.domain import NetworkRoute -except ImportError: - NetworkRoute = None - - -class AnsibleHcloudRoute(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_route") - self.hcloud_network = None - self.hcloud_route = None - - def _prepare_result(self): - return { - "network": to_native(self.hcloud_network.name), - "destination": to_native(self.hcloud_route.destination), - "gateway": self.hcloud_route.gateway, - } - - def _get_network(self): - try: - self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) - self.hcloud_route = None - except Exception as e: - self.module.fail_json(msg=e.message) - - def _get_route(self): - destination = self.module.params.get("destination") - gateway = self.module.params.get("gateway") - for route in self.hcloud_network.routes: - if route.destination == destination and route.gateway == gateway: - self.hcloud_route = route - - def _create_route(self): - route = NetworkRoute( - destination=self.module.params.get("destination"), - gateway=self.module.params.get('gateway') - ) - - if not self.module.check_mode: - try: - self.hcloud_network.add_route(route=route).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - - self._mark_as_changed() - self._get_network() - self._get_route() - - def present_route(self): - self._get_network() - self._get_route() - if self.hcloud_route is None: - self._create_route() - - def delete_route(self): - self._get_network() - self._get_route() - if self.hcloud_route is not None and self.hcloud_network is not None: - if not self.module.check_mode: - try: - self.hcloud_network.delete_route(self.hcloud_route).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_route = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - network={"type": "str", "required": True}, - gateway={"type": "str", "required": True}, - destination={"type": "str", "required": True}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudRoute.define_module() - - hcloud = AnsibleHcloudRoute(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_route() - elif state == "present": - hcloud.present_route() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py deleted file mode 100644 index 3a77da695..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py +++ /dev/null @@ -1,928 +0,0 @@ -#!/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_server - -short_description: Create and manage cloud servers on the Hetzner Cloud. - - -description: - - Create, update and manage cloud servers on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Hetzner Cloud server to manage. - - Only required if no server I(name) is given - type: int - name: - description: - - The Name of the Hetzner Cloud server to manage. - - Only required if no server I(id) is given or a server does not exist. - type: str - server_type: - description: - - The Server Type of the Hetzner Cloud server to manage. - - Required if server does not exist. - type: str - ssh_keys: - description: - - List of SSH key names - - The key names correspond to the SSH keys configured for your - Hetzner Cloud account access. - type: list - elements: str - volumes: - description: - - List of Volumes IDs that should be attached to the server on server creation. - type: list - elements: str - firewalls: - description: - - List of Firewall IDs that should be attached to the server on server creation. - type: list - elements: str - image: - description: - - Image the server should be created from. - - Required if server does not exist. - type: str - location: - description: - - Location of Server. - - Required if no I(datacenter) is given and server does not exist. - type: str - datacenter: - description: - - Datacenter of Server. - - Required of no I(location) is given and server does not exist. - type: str - backups: - description: - - Enable or disable Backups for the given Server. - type: bool - upgrade_disk: - description: - - Resize the disk size, when resizing a server. - - If you want to downgrade the server later, this value should be False. - type: bool - default: no - enable_ipv4: - description: - - Enables the public ipv4 address - type: bool - default: yes - enable_ipv6: - description: - - Enables the public ipv6 address - type: bool - default: yes - ipv4: - description: - - ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a new ipv4 Primary IP will automatically be created - type: str - ipv6: - description: - - ID of the ipv6 Primary IP to use. If omitted and enable_ipv6 is true, a new ipv6 Primary IP will automatically be created. - type: str - private_networks: - description: - - List of private networks the server is attached to (name or ID) - - If None, private networks are left as they are (e.g. if previously added by hcloud_server_network), - if it has any other value (including []), only those networks are attached to the server. - type: list - elements: str - force_upgrade: - description: - - Deprecated - - Force the upgrade of the server. - - Power off the server if it is running on upgrade. - type: bool - default: no - force: - description: - - Force the update of the server. - - May power off the server if update. - type: bool - default: no - allow_deprecated_image: - description: - - Allows the creation of servers with deprecated images. - type: bool - default: no - user_data: - description: - - User Data to be passed to the server on creation. - - Only used if server does not exist. - type: str - rescue_mode: - description: - - Add the Hetzner rescue system type you want the server to be booted into. - type: str - labels: - description: - - User-defined labels (key-value pairs). - type: dict - delete_protection: - description: - - Protect the Server for deletion. - - Needs to be the same as I(rebuild_protection). - type: bool - rebuild_protection: - description: - - Protect the Server for rebuild. - - Needs to be the same as I(delete_protection). - type: bool - placement_group: - description: - - Placement Group of the server. - type: str - state: - description: - - State of the server. - default: present - choices: [ absent, present, restarted, started, stopped, rebuild ] - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic server - hcloud_server: - name: my-server - server_type: cx11 - image: ubuntu-18.04 - state: present - -- name: Create a basic server with ssh key - hcloud_server: - name: my-server - server_type: cx11 - image: ubuntu-18.04 - location: fsn1 - ssh_keys: - - me@myorganisation - state: present - -- name: Resize an existing server - hcloud_server: - name: my-server - server_type: cx21 - upgrade_disk: yes - state: present - -- name: Ensure the server is absent (remove if needed) - hcloud_server: - name: my-server - state: absent - -- name: Ensure the server is started - hcloud_server: - name: my-server - state: started - -- name: Ensure the server is stopped - hcloud_server: - name: my-server - state: stopped - -- name: Ensure the server is restarted - hcloud_server: - name: my-server - state: restarted - -- name: Ensure the server is will be booted in rescue mode and therefore restarted - hcloud_server: - name: my-server - rescue_mode: linux64 - state: restarted - -- name: Ensure the server is rebuild - hcloud_server: - name: my-server - image: ubuntu-18.04 - state: rebuild - -- name: Add server to placement group - hcloud_server: - name: my-server - placement_group: my-placement-group - force: True - state: present - -- name: Remove server from placement group - hcloud_server: - name: my-server - placement_group: null - state: present - -- name: Add server with private network only - hcloud_server: - name: my-server - enable_ipv4: false - enable_ipv6: false - private_networks: - - my-network - - 4711 - state: present -""" - -RETURN = """ -hcloud_server: - description: The server instance - returned: Always - type: complex - contains: - id: - description: Numeric identifier of the server - returned: always - type: int - sample: 1937415 - name: - description: Name of the server - returned: always - type: str - sample: my-server - status: - description: Status of the server - returned: always - type: str - sample: running - server_type: - description: Name of the server type of the server - returned: always - type: str - sample: cx11 - ipv4_address: - description: Public IPv4 address of the server - returned: always - type: str - sample: 116.203.104.109 - ipv6: - description: IPv6 network of the server - returned: always - type: str - sample: 2a01:4f8:1c1c:c140::/64 - private_networks: - description: List of private networks the server is attached to (name or ID) - returned: always - type: list - elements: str - sample: ['my-network', 'another-network', '4711'] - private_networks_info: - description: List of private networks the server is attached to (dict with name and ip) - returned: always - type: list - elements: dict - sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] - location: - description: Name of the location of the server - returned: always - type: str - sample: fsn1 - placement_group: - description: Placement Group of the server - type: str - returned: always - sample: 4711 - version_added: "1.5.0" - datacenter: - description: Name of the datacenter of the server - returned: always - type: str - sample: fsn1-dc14 - rescue_enabled: - description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot - returned: always - type: bool - sample: false - backup_window: - description: Time window (UTC) in which the backup will run, or null if the backups are not enabled - returned: always - type: bool - sample: 22-02 - labels: - description: User-defined labels (key-value pairs) - returned: always - type: dict - delete_protection: - description: True if server is protected for deletion - type: bool - returned: always - sample: false - version_added: "0.1.0" - rebuild_protection: - description: True if server is protected for rebuild - type: bool - returned: always - sample: false - version_added: "0.1.0" -""" - -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 -from datetime import timedelta - -try: - from hcloud.volumes.domain import Volume - from hcloud.ssh_keys.domain import SSHKey - from hcloud.servers.domain import Server, ServerCreatePublicNetwork - from hcloud.firewalls.domain import FirewallResource -except ImportError: - Volume = None - SSHKey = None - Server = None - ServerCreatePublicNetwork = None - FirewallResource = None - - -class AnsibleHcloudServer(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_server") - self.hcloud_server = None - - def _prepare_result(self): - image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name) - placement_group = None if self.hcloud_server.placement_group is None else to_native( - self.hcloud_server.placement_group.name) - ipv4_address = None if self.hcloud_server.public_net.ipv4 is None else to_native( - self.hcloud_server.public_net.ipv4.ip) - ipv6 = None if self.hcloud_server.public_net.ipv6 is None else to_native(self.hcloud_server.public_net.ipv6.ip) - backup_window = None if self.hcloud_server.backup_window is None else to_native(self.hcloud_server.backup_window) - return { - "id": to_native(self.hcloud_server.id), - "name": to_native(self.hcloud_server.name), - "ipv4_address": ipv4_address, - "ipv6": ipv6, - "private_networks": [to_native(net.network.name) for net in self.hcloud_server.private_net], - "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in self.hcloud_server.private_net], - "image": image, - "server_type": to_native(self.hcloud_server.server_type.name), - "datacenter": to_native(self.hcloud_server.datacenter.name), - "location": to_native(self.hcloud_server.datacenter.location.name), - "placement_group": placement_group, - "rescue_enabled": self.hcloud_server.rescue_enabled, - "backup_window": backup_window, - "labels": self.hcloud_server.labels, - "delete_protection": self.hcloud_server.protection["delete"], - "rebuild_protection": self.hcloud_server.protection["rebuild"], - "status": to_native(self.hcloud_server.status), - } - - def _get_server(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_server = self.client.servers.get_by_id( - self.module.params.get("id") - ) - else: - self.hcloud_server = self.client.servers.get_by_name( - self.module.params.get("name") - ) - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_server(self): - self.module.fail_on_missing_params( - required_params=["name", "server_type", "image"] - ) - - server_type = self._get_server_type() - - params = { - "name": self.module.params.get("name"), - "server_type": server_type, - "user_data": self.module.params.get("user_data"), - "labels": self.module.params.get("labels"), - "image": self._get_image(server_type), - "placement_group": self._get_placement_group(), - "public_net": ServerCreatePublicNetwork( - enable_ipv4=self.module.params.get("enable_ipv4"), - enable_ipv6=self.module.params.get("enable_ipv6") - ) - } - - if self.module.params.get("ipv4") is not None: - p = self.client.primary_ips.get_by_name(self.module.params.get("ipv4")) - if not p: - p = self.client.primary_ips.get_by_id(self.module.params.get("ipv4")) - params["public_net"].ipv4 = p - - if self.module.params.get("ipv6") is not None: - p = self.client.primary_ips.get_by_name(self.module.params.get("ipv6")) - if not p: - p = self.client.primary_ips.get_by_id(self.module.params.get("ipv6")) - params["public_net"].ipv6 = p - - if self.module.params.get("private_networks") is not None: - _networks = [] - for network_name_or_id in self.module.params.get("private_networks"): - _networks.append( - self.client.networks.get_by_name(network_name_or_id) - or self.client.networks.get_by_id(network_name_or_id) - ) - params["networks"] = _networks - - if self.module.params.get("ssh_keys") is not None: - params["ssh_keys"] = [ - SSHKey(name=ssh_key_name) - for ssh_key_name in self.module.params.get("ssh_keys") - ] - - if self.module.params.get("volumes") is not None: - params["volumes"] = [ - Volume(id=volume_id) for volume_id in self.module.params.get("volumes") - ] - if self.module.params.get("firewalls") is not None: - params["firewalls"] = [] - for fw in self.module.params.get("firewalls"): - f = self.client.firewalls.get_by_name(fw) - if f is not None: - # When firewall name is not available look for id instead - params["firewalls"].append(f) - else: - params["firewalls"].append(self.client.firewalls.get_by_id(fw)) - - if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: - # When not given, the API will choose the location. - params["location"] = None - params["datacenter"] = None - elif self.module.params.get("location") is not None and self.module.params.get("datacenter") 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("datacenter") is not None: - params["datacenter"] = self.client.datacenters.get_by_name( - self.module.params.get("datacenter") - ) - - if self.module.params.get("state") == "stopped": - params["start_after_create"] = False - if not self.module.check_mode: - try: - resp = self.client.servers.create(**params) - self.result["root_password"] = resp.root_password - resp.action.wait_until_finished(max_retries=1000) - [action.wait_until_finished() for action in resp.next_actions] - - rescue_mode = self.module.params.get("rescue_mode") - if rescue_mode: - self._get_server() - self._set_rescue_mode(rescue_mode) - - backups = self.module.params.get("backups") - if backups: - self._get_server() - self.hcloud_server.enable_backup().wait_until_finished() - - delete_protection = self.module.params.get("delete_protection") - rebuild_protection = self.module.params.get("rebuild_protection") - if delete_protection is not None and rebuild_protection is not None: - self._get_server() - self.hcloud_server.change_protection(delete=delete_protection, - rebuild=rebuild_protection).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_server() - - def _get_image(self, server_type): - image_resp = self.client.images.get_list(name=self.module.params.get("image"), architecture=server_type.architecture, include_deprecated=True) - images = getattr(image_resp, 'images') - image = None - if images is not None and len(images) > 0: - # If image name is not available look for id instead - image = images[0] - else: - try: - image = self.client.images.get_by_id(self.module.params.get("image")) - except Exception: - self.module.fail_json(msg="Image %s was not found" % self.module.params.get('image')) - if image.deprecated is not None: - available_until = image.deprecated + timedelta(days=90) - if self.module.params.get("allow_deprecated_image"): - self.module.warn( - "You try to use a deprecated image. The image %s will continue to be available until %s.") % ( - image.name, available_until.strftime('%Y-%m-%d')) - else: - self.module.fail_json( - msg=("You try to use a deprecated image. The image %s will continue to be available until %s." + - " If you want to use this image use allow_deprecated_image=yes." - ) % (image.name, available_until.strftime('%Y-%m-%d'))) - return image - - def _get_server_type(self): - server_type = self.client.server_types.get_by_name( - self.module.params.get("server_type") - ) - if server_type is None: - try: - server_type = self.client.server_types.get_by_id(self.module.params.get("server_type")) - except Exception: - self.module.fail_json(msg="server_type %s was not found" % self.module.params.get('server_type')) - - return server_type - - def _get_placement_group(self): - if self.module.params.get("placement_group") is None: - return None - - placement_group = self.client.placement_groups.get_by_name( - self.module.params.get("placement_group") - ) - if placement_group is None: - try: - placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group")) - except Exception: - self.module.fail_json( - msg="placement_group %s was not found" % self.module.params.get("placement_group")) - - return placement_group - - def _get_primary_ip(self, field): - if self.module.params.get(field) is None: - return None - - primary_ip = self.client.primary_ips.get_by_name( - self.module.params.get(field) - ) - if primary_ip is None: - try: - primary_ip = self.client.primary_ips.get_by_id(self.module.params.get(field)) - except Exception as e: - self.module.fail_json( - msg="primary_ip %s was not found" % self.module.params.get(field)) - - return primary_ip - - def _update_server(self): - if "force_upgrade" in self.module.params: - self.module.warn("force_upgrade is deprecated, use force instead") - - try: - previous_server_status = self.hcloud_server.status - - rescue_mode = self.module.params.get("rescue_mode") - if rescue_mode and self.hcloud_server.rescue_enabled is False: - if not self.module.check_mode: - self._set_rescue_mode(rescue_mode) - self._mark_as_changed() - elif not rescue_mode and self.hcloud_server.rescue_enabled is True: - if not self.module.check_mode: - self.hcloud_server.disable_rescue().wait_until_finished() - self._mark_as_changed() - - backups = self.module.params.get("backups") - if backups and self.hcloud_server.backup_window is None: - if not self.module.check_mode: - self.hcloud_server.enable_backup().wait_until_finished() - self._mark_as_changed() - elif backups is not None and not backups and self.hcloud_server.backup_window is not None: - if not self.module.check_mode: - self.hcloud_server.disable_backup().wait_until_finished() - self._mark_as_changed() - - labels = self.module.params.get("labels") - if labels is not None and labels != self.hcloud_server.labels: - if not self.module.check_mode: - self.hcloud_server.update(labels=labels) - self._mark_as_changed() - - wanted_firewalls = self.module.params.get("firewalls") - if wanted_firewalls is not None: - # Removing existing but not wanted firewalls - for current_firewall in self.hcloud_server.public_net.firewalls: - if current_firewall.firewall.name not in wanted_firewalls: - self._mark_as_changed() - if not self.module.check_mode: - r = FirewallResource(type="server", server=self.hcloud_server) - actions = self.client.firewalls.remove_from_resources(current_firewall.firewall, [r]) - for a in actions: - a.wait_until_finished() - - # Adding wanted firewalls that doesn't exist yet - for fname in wanted_firewalls: - found = False - for f in self.hcloud_server.public_net.firewalls: - if f.firewall.name == fname: - found = True - break - - if not found: - self._mark_as_changed() - if not self.module.check_mode: - fw = self.client.firewalls.get_by_name(fname) - if fw is None: - self.module.fail_json(msg="firewall %s was not found" % fname) - r = FirewallResource(type="server", server=self.hcloud_server) - actions = self.client.firewalls.apply_to_resources(fw, [r]) - for a in actions: - a.wait_until_finished() - - if "placement_group" in self.module.params: - if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None: - if not self.module.check_mode: - self.hcloud_server.remove_from_placement_group().wait_until_finished() - self._mark_as_changed() - else: - placement_group = self._get_placement_group() - if ( - placement_group is not None and - ( - self.hcloud_server.placement_group is None or - self.hcloud_server.placement_group.id != placement_group.id - ) - ): - self.stop_server_if_forced() - if not self.module.check_mode: - self.hcloud_server.add_to_placement_group(placement_group).wait_until_finished() - self._mark_as_changed() - - if "ipv4" in self.module.params: - if ( - self.module.params["ipv4"] is None and - self.hcloud_server.public_net.primary_ipv4 is not None and - not self.module.params.get("enable_ipv4") - ): - self.stop_server_if_forced() - if not self.module.check_mode: - self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished() - self._mark_as_changed() - else: - primary_ip = self._get_primary_ip("ipv4") - if ( - primary_ip is not None and - ( - self.hcloud_server.public_net.primary_ipv4 is None or - self.hcloud_server.public_net.primary_ipv4.id != primary_ip.id - ) - ): - self.stop_server_if_forced() - if not self.module.check_mode: - if self.hcloud_server.public_net.primary_ipv4: - self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished() - primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished() - self._mark_as_changed() - if "ipv6" in self.module.params: - if ( - (self.module.params["ipv6"] is None or self.module.params["ipv6"] == "") and - self.hcloud_server.public_net.primary_ipv6 is not None and - not self.module.params.get("enable_ipv6") - ): - self.stop_server_if_forced() - if not self.module.check_mode: - self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished() - self._mark_as_changed() - else: - primary_ip = self._get_primary_ip("ipv6") - if ( - primary_ip is not None and - ( - self.hcloud_server.public_net.primary_ipv6 is None or - self.hcloud_server.public_net.primary_ipv6.id != primary_ip.id - ) - ): - self.stop_server_if_forced() - if not self.module.check_mode: - if self.hcloud_server.public_net.primary_ipv6 is not None: - self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished() - primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished() - self._mark_as_changed() - if "private_networks" in self.module.params and self.module.params["private_networks"] is not None: - if not bool(self.module.params["private_networks"]): - # This handles None, "" and [] - networks_target = {} - else: - _networks = {} - for network_name_or_id in self.module.params.get("private_networks"): - _found_network = self.client.networks.get_by_name(network_name_or_id) \ - or self.client.networks.get_by_id(network_name_or_id) - _networks.update( - {_found_network.id: _found_network} - ) - networks_target = _networks - networks_is = dict() - for p_network in self.hcloud_server.private_net: - networks_is.update({p_network.network.id: p_network.network}) - for network_id in set(list(networks_is) + list(networks_target)): - if network_id in networks_is and network_id not in networks_target: - self.stop_server_if_forced() - if not self.module.check_mode: - self.hcloud_server.detach_from_network(networks_is[network_id]).wait_until_finished() - self._mark_as_changed() - elif network_id in networks_target and network_id not in networks_is: - self.stop_server_if_forced() - if not self.module.check_mode: - self.hcloud_server.attach_to_network(networks_target[network_id]).wait_until_finished() - self._mark_as_changed() - - server_type = self.module.params.get("server_type") - if server_type is not None and self.hcloud_server.server_type.name != server_type: - self.stop_server_if_forced() - - timeout = 100 - if self.module.params.get("upgrade_disk"): - timeout = ( - 1000 - ) # When we upgrade the disk to the resize progress takes some more time. - if not self.module.check_mode: - self.hcloud_server.change_type( - server_type=self._get_server_type(), - upgrade_disk=self.module.params.get("upgrade_disk"), - ).wait_until_finished(timeout) - self._mark_as_changed() - - if ( - not self.module.check_mode and - ( - ( - self.module.params.get("state") == "present" and - previous_server_status == Server.STATUS_RUNNING - ) or - self.module.params.get("state") == "started" - ) - ): - self.start_server() - - delete_protection = self.module.params.get("delete_protection") - rebuild_protection = self.module.params.get("rebuild_protection") - if (delete_protection is not None and rebuild_protection is not None) and ( - delete_protection != self.hcloud_server.protection["delete"] or rebuild_protection != - self.hcloud_server.protection["rebuild"]): - if not self.module.check_mode: - self.hcloud_server.change_protection(delete=delete_protection, - rebuild=rebuild_protection).wait_until_finished() - self._mark_as_changed() - self._get_server() - except Exception as e: - self.module.fail_json(msg=e) - - def _set_rescue_mode(self, rescue_mode): - if self.module.params.get("ssh_keys"): - resp = self.hcloud_server.enable_rescue(type=rescue_mode, - ssh_keys=[self.client.ssh_keys.get_by_name(ssh_key_name).id - for - ssh_key_name in - self.module.params.get("ssh_keys")]) - else: - resp = self.hcloud_server.enable_rescue(type=rescue_mode) - resp.action.wait_until_finished() - self.result["root_password"] = resp.root_password - - def start_server(self): - try: - if self.hcloud_server: - if self.hcloud_server.status != Server.STATUS_RUNNING: - if not self.module.check_mode: - self.client.servers.power_on(self.hcloud_server).wait_until_finished() - self._mark_as_changed() - self._get_server() - except Exception as e: - self.module.fail_json(msg=e.message) - - def stop_server(self): - try: - if self.hcloud_server: - if self.hcloud_server.status != Server.STATUS_OFF: - if not self.module.check_mode: - self.client.servers.power_off(self.hcloud_server).wait_until_finished() - self._mark_as_changed() - self._get_server() - except Exception as e: - self.module.fail_json(msg=e.message) - - def stop_server_if_forced(self): - previous_server_status = self.hcloud_server.status - if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode: - if ( - self.module.params.get("force_upgrade") or - self.module.params.get("force") or - self.module.params.get("state") == "stopped" - ): - self.stop_server() # Only stopped server can be upgraded - return previous_server_status - else: - self.module.warn( - "You can not upgrade a running instance %s. You need to stop the instance or use force=yes." - % self.hcloud_server.name - ) - - return None - - def rebuild_server(self): - self.module.fail_on_missing_params( - required_params=["image"] - ) - try: - if not self.module.check_mode: - image = self._get_image(self.hcloud_server.server_type) - self.client.servers.rebuild(self.hcloud_server, image).wait_until_finished(1000) # When we rebuild the server progress takes some more time. - self._mark_as_changed() - - self._get_server() - except Exception as e: - self.module.fail_json(msg=e.message) - - def present_server(self): - self._get_server() - if self.hcloud_server is None: - self._create_server() - else: - self._update_server() - - def delete_server(self): - try: - self._get_server() - if self.hcloud_server is not None: - if not self.module.check_mode: - self.client.servers.delete(self.hcloud_server).wait_until_finished() - self._mark_as_changed() - self.hcloud_server = None - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - image={"type": "str"}, - server_type={"type": "str"}, - location={"type": "str"}, - datacenter={"type": "str"}, - user_data={"type": "str"}, - ssh_keys={"type": "list", "elements": "str", "no_log": False}, - volumes={"type": "list", "elements": "str"}, - firewalls={"type": "list", "elements": "str"}, - labels={"type": "dict"}, - backups={"type": "bool"}, - upgrade_disk={"type": "bool", "default": False}, - enable_ipv4={"type": "bool", "default": True}, - enable_ipv6={"type": "bool", "default": True}, - ipv4={"type": "str"}, - ipv6={"type": "str"}, - private_networks={"type": "list", "elements": "str", "default": None}, - force={"type": "bool", "default": False}, - force_upgrade={"type": "bool", "default": False}, - allow_deprecated_image={"type": "bool", "default": False}, - rescue_mode={"type": "str"}, - delete_protection={"type": "bool"}, - rebuild_protection={"type": "bool"}, - placement_group={"type": "str"}, - state={ - "choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name']], - mutually_exclusive=[["location", "datacenter"]], - required_together=[["delete_protection", "rebuild_protection"]], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudServer.define_module() - - hcloud = AnsibleHcloudServer(module) - state = module.params.get("state") - if state == "absent": - hcloud.delete_server() - elif state == "present": - hcloud.present_server() - elif state == "started": - hcloud.present_server() - hcloud.start_server() - elif state == "stopped": - hcloud.present_server() - hcloud.stop_server() - elif state == "restarted": - hcloud.present_server() - hcloud.stop_server() - hcloud.start_server() - elif state == "rebuild": - hcloud.present_server() - hcloud.rebuild_server() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py deleted file mode 100644 index 102ceec0d..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/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_server_info - -short_description: Gather infos about your Hetzner Cloud servers. - - -description: - - Gather infos about your Hetzner Cloud servers. - - This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts). - Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the server you want to get. - type: int - name: - description: - - The name of the server you want to get. - type: str - label_selector: - description: - - The label selector for the server you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud server infos - hcloud_server_info: - register: output - -- name: Print the gathered infos - debug: - var: output.hcloud_server_info -""" - -RETURN = """ -hcloud_server_info: - description: The server infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the server - returned: always - type: int - sample: 1937415 - name: - description: Name of the server - returned: always - type: str - sample: my-server - status: - description: Status of the server - returned: always - type: str - sample: running - server_type: - description: Name of the server type of the server - returned: always - type: str - sample: cx11 - ipv4_address: - description: Public IPv4 address of the server - returned: always - type: str - sample: 116.203.104.109 - ipv6: - description: IPv6 network of the server - returned: always - type: str - sample: 2a01:4f8:1c1c:c140::/64 - private_networks: - description: List of private networks the server is attached to (name) - returned: always - type: list - elements: str - sample: ['my-network', 'another-network'] - private_networks_info: - description: List of private networks the server is attached to (dict with name and ip) - returned: always - type: list - elements: dict - sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] - location: - description: Name of the location of the server - returned: always - type: str - sample: fsn1 - placement_group: - description: Placement Group of the server - type: str - returned: always - sample: 4711 - version_added: "1.5.0" - datacenter: - description: Name of the datacenter of the server - returned: always - type: str - sample: fsn1-dc14 - rescue_enabled: - description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot - returned: always - type: bool - sample: false - backup_window: - description: Time window (UTC) in which the backup will run, or null if the backups are not enabled - returned: always - type: bool - sample: 22-02 - labels: - description: User-defined labels (key-value pairs) - returned: always - type: dict - delete_protection: - description: True if server is protected for deletion - type: bool - returned: always - sample: false - version_added: "0.1.0" - rebuild_protection: - description: True if server is protected for rebuild - type: bool - returned: always - sample: false - version_added: "0.1.0" -""" - -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 - - -class AnsibleHcloudServerInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_server_info") - self.hcloud_server_info = None - - def _prepare_result(self): - tmp = [] - - for server in self.hcloud_server_info: - if server is not None: - image = None if server.image is None else to_native(server.image.name) - placement_group = None if server.placement_group is None else to_native(server.placement_group.name) - ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) - ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) - backup_window = None if server.backup_window is None else to_native(server.backup_window) - tmp.append({ - "id": to_native(server.id), - "name": to_native(server.name), - "ipv4_address": ipv4_address, - "ipv6": ipv6, - "private_networks": [to_native(net.network.name) for net in server.private_net], - "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net], - "image": image, - "server_type": to_native(server.server_type.name), - "datacenter": to_native(server.datacenter.name), - "location": to_native(server.datacenter.location.name), - "placement_group": placement_group, - "rescue_enabled": server.rescue_enabled, - "backup_window": backup_window, - "labels": server.labels, - "status": to_native(server.status), - "delete_protection": server.protection["delete"], - "rebuild_protection": server.protection["rebuild"], - }) - return tmp - - def get_servers(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_server_info = [self.client.servers.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_server_info = [self.client.servers.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_server_info = self.client.servers.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_server_info = self.client.servers.get_all() - - except Exception 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 = AnsibleHcloudServerInfo.define_module() - - is_old_facts = module._name == 'hcloud_server_facts' - if is_old_facts: - module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudServerInfo(module) - hcloud.get_servers() - result = hcloud.get_result() - - if is_old_facts: - ansible_info = { - 'hcloud_server_facts': result['hcloud_server_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_server_info': result['hcloud_server_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py deleted file mode 100644 index 102ceec0d..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/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_server_info - -short_description: Gather infos about your Hetzner Cloud servers. - - -description: - - Gather infos about your Hetzner Cloud servers. - - This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts). - Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the server you want to get. - type: int - name: - description: - - The name of the server you want to get. - type: str - label_selector: - description: - - The label selector for the server you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud server infos - hcloud_server_info: - register: output - -- name: Print the gathered infos - debug: - var: output.hcloud_server_info -""" - -RETURN = """ -hcloud_server_info: - description: The server infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the server - returned: always - type: int - sample: 1937415 - name: - description: Name of the server - returned: always - type: str - sample: my-server - status: - description: Status of the server - returned: always - type: str - sample: running - server_type: - description: Name of the server type of the server - returned: always - type: str - sample: cx11 - ipv4_address: - description: Public IPv4 address of the server - returned: always - type: str - sample: 116.203.104.109 - ipv6: - description: IPv6 network of the server - returned: always - type: str - sample: 2a01:4f8:1c1c:c140::/64 - private_networks: - description: List of private networks the server is attached to (name) - returned: always - type: list - elements: str - sample: ['my-network', 'another-network'] - private_networks_info: - description: List of private networks the server is attached to (dict with name and ip) - returned: always - type: list - elements: dict - sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] - location: - description: Name of the location of the server - returned: always - type: str - sample: fsn1 - placement_group: - description: Placement Group of the server - type: str - returned: always - sample: 4711 - version_added: "1.5.0" - datacenter: - description: Name of the datacenter of the server - returned: always - type: str - sample: fsn1-dc14 - rescue_enabled: - description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot - returned: always - type: bool - sample: false - backup_window: - description: Time window (UTC) in which the backup will run, or null if the backups are not enabled - returned: always - type: bool - sample: 22-02 - labels: - description: User-defined labels (key-value pairs) - returned: always - type: dict - delete_protection: - description: True if server is protected for deletion - type: bool - returned: always - sample: false - version_added: "0.1.0" - rebuild_protection: - description: True if server is protected for rebuild - type: bool - returned: always - sample: false - version_added: "0.1.0" -""" - -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 - - -class AnsibleHcloudServerInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_server_info") - self.hcloud_server_info = None - - def _prepare_result(self): - tmp = [] - - for server in self.hcloud_server_info: - if server is not None: - image = None if server.image is None else to_native(server.image.name) - placement_group = None if server.placement_group is None else to_native(server.placement_group.name) - ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) - ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) - backup_window = None if server.backup_window is None else to_native(server.backup_window) - tmp.append({ - "id": to_native(server.id), - "name": to_native(server.name), - "ipv4_address": ipv4_address, - "ipv6": ipv6, - "private_networks": [to_native(net.network.name) for net in server.private_net], - "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net], - "image": image, - "server_type": to_native(server.server_type.name), - "datacenter": to_native(server.datacenter.name), - "location": to_native(server.datacenter.location.name), - "placement_group": placement_group, - "rescue_enabled": server.rescue_enabled, - "backup_window": backup_window, - "labels": server.labels, - "status": to_native(server.status), - "delete_protection": server.protection["delete"], - "rebuild_protection": server.protection["rebuild"], - }) - return tmp - - def get_servers(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_server_info = [self.client.servers.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_server_info = [self.client.servers.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_server_info = self.client.servers.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_server_info = self.client.servers.get_all() - - except Exception 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 = AnsibleHcloudServerInfo.define_module() - - is_old_facts = module._name == 'hcloud_server_facts' - if is_old_facts: - module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudServerInfo(module) - hcloud.get_servers() - result = hcloud.get_result() - - if is_old_facts: - ansible_info = { - 'hcloud_server_facts': result['hcloud_server_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_server_info': result['hcloud_server_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py deleted file mode 100644 index 79f6838fd..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py +++ /dev/null @@ -1,246 +0,0 @@ -#!/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_server_network - -short_description: Manage the relationship between Hetzner Cloud Networks and servers - - -description: - - Create and delete the relationship Hetzner Cloud Networks and servers - -author: - - Lukas Kaemmerling (@lkaemmerling) - -options: - network: - description: - - The name of the Hetzner Cloud Networks. - type: str - required: true - server: - description: - - The name of the Hetzner Cloud server. - type: str - required: true - ip: - description: - - The IP the server should have. - type: str - alias_ips: - description: - - Alias IPs the server has. - type: list - elements: str - state: - description: - - State of the server_network. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.3.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic server network - hcloud_server_network: - network: my-network - server: my-server - state: present - -- name: Create a server network and specify the ip address - hcloud_server_network: - network: my-network - server: my-server - ip: 10.0.0.1 - state: present - -- name: Create a server network and add alias ips - hcloud_server_network: - network: my-network - server: my-server - ip: 10.0.0.1 - alias_ips: - - 10.1.0.1 - - 10.2.0.1 - state: present - -- name: Ensure the server network is absent (remove if needed) - hcloud_server_network: - network: my-network - server: my-server - state: absent -""" - -RETURN = """ -hcloud_server_network: - description: The relationship between a server and a network - returned: always - type: complex - contains: - network: - description: Name of the Network - type: str - returned: always - sample: my-network - server: - description: Name of the server - type: str - returned: always - sample: my-server - ip: - description: IP of the server within the Network ip range - type: str - returned: always - sample: 10.0.0.8 - alias_ips: - description: Alias IPs of the server within the Network ip range - type: str - returned: always - sample: [10.1.0.1, ...] -""" - -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 - - -class AnsibleHcloudServerNetwork(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_server_network") - self.hcloud_network = None - self.hcloud_server = None - self.hcloud_server_network = None - - def _prepare_result(self): - return { - "network": to_native(self.hcloud_network.name), - "server": to_native(self.hcloud_server.name), - "ip": to_native(self.hcloud_server_network.ip), - "alias_ips": self.hcloud_server_network.alias_ips, - } - - def _get_server_and_network(self): - try: - self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) - self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("server")) - self.hcloud_server_network = None - except Exception as e: - self.module.fail_json(msg=e.message) - - def _get_server_network(self): - for privateNet in self.hcloud_server.private_net: - if privateNet.network.id == self.hcloud_network.id: - self.hcloud_server_network = privateNet - - def _create_server_network(self): - params = { - "network": self.hcloud_network - } - - if self.module.params.get("ip") is not None: - params["ip"] = self.module.params.get("ip") - if self.module.params.get("alias_ips") is not None: - params["alias_ips"] = self.module.params.get("alias_ips") - - if not self.module.check_mode: - try: - self.hcloud_server.attach_to_network(**params).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - - self._mark_as_changed() - self._get_server_and_network() - self._get_server_network() - - def _update_server_network(self): - params = { - "network": self.hcloud_network - } - alias_ips = self.module.params.get("alias_ips") - if alias_ips is not None and sorted(self.hcloud_server_network.alias_ips) != sorted(alias_ips): - params["alias_ips"] = alias_ips - - if not self.module.check_mode: - try: - self.hcloud_server.change_alias_ips(**params).wait_until_finished() - except APIException as e: - self.module.fail_json(msg=e.message) - - self._mark_as_changed() - self._get_server_and_network() - self._get_server_network() - - def present_server_network(self): - self._get_server_and_network() - self._get_server_network() - if self.hcloud_server_network is None: - self._create_server_network() - else: - self._update_server_network() - - def delete_server_network(self): - self._get_server_and_network() - self._get_server_network() - if self.hcloud_server_network is not None and self.hcloud_server is not None: - if not self.module.check_mode: - try: - self.hcloud_server.detach_from_network(self.hcloud_server_network.network).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_server_network = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - network={"type": "str", "required": True}, - server={"type": "str", "required": True}, - ip={"type": "str"}, - alias_ips={"type": "list", "elements": "str"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudServerNetwork.define_module() - - hcloud = AnsibleHcloudServerNetwork(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_server_network() - elif state == "present": - hcloud.present_server_network() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py deleted file mode 100644 index a84067c32..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/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_server_type_info - -short_description: Gather infos about the Hetzner Cloud server types. - - -description: - - Gather infos about your Hetzner Cloud server types. - - This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts). - Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the server type you want to get. - type: int - name: - description: - - The name of the server type you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud server type infos - hcloud_server_type_info: - register: output - -- name: Print the gathered infos - debug: - var: output.hcloud_server_type_info -""" - -RETURN = """ -hcloud_server_type_info: - description: The server type infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the server type - returned: always - type: int - sample: 1937415 - name: - description: Name of the server type - returned: always - type: str - sample: fsn1 - description: - description: Detail description of the server type - returned: always - type: str - sample: Falkenstein DC Park 1 - cores: - description: Number of cpu cores a server of this type will have - returned: always - type: int - sample: 1 - memory: - description: Memory a server of this type will have in GB - returned: always - type: int - sample: 1 - disk: - description: Disk size a server of this type will have in GB - returned: always - type: int - sample: 25 - storage_type: - description: Type of server boot drive - returned: always - type: str - sample: local - cpu_type: - description: Type of cpu - returned: always - type: str - sample: shared - architecture: - description: Architecture of cpu - returned: always - type: str - sample: x86 -""" - -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 - - -class AnsibleHcloudServerTypeInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_server_type_info") - self.hcloud_server_type_info = None - - def _prepare_result(self): - tmp = [] - - for server_type in self.hcloud_server_type_info: - if server_type is not None: - tmp.append({ - "id": to_native(server_type.id), - "name": to_native(server_type.name), - "description": to_native(server_type.description), - "cores": server_type.cores, - "memory": server_type.memory, - "disk": server_type.disk, - "storage_type": to_native(server_type.storage_type), - "cpu_type": to_native(server_type.cpu_type), - "architecture": to_native(server_type.architecture) - }) - return tmp - - def get_server_types(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_server_type_info = [self.client.server_types.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_server_type_info = [self.client.server_types.get_by_name( - self.module.params.get("name") - )] - else: - self.hcloud_server_type_info = self.client.server_types.get_all() - - except Exception 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 = AnsibleHcloudServerTypeInfo.define_module() - - is_old_facts = module._name == 'hcloud_server_type_facts' - if is_old_facts: - module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudServerTypeInfo(module) - hcloud.get_server_types() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_server_type_info': result['hcloud_server_type_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_server_type_info': result['hcloud_server_type_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py deleted file mode 100644 index a84067c32..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/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_server_type_info - -short_description: Gather infos about the Hetzner Cloud server types. - - -description: - - Gather infos about your Hetzner Cloud server types. - - This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts). - Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)! - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the server type you want to get. - type: int - name: - description: - - The name of the server type you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud server type infos - hcloud_server_type_info: - register: output - -- name: Print the gathered infos - debug: - var: output.hcloud_server_type_info -""" - -RETURN = """ -hcloud_server_type_info: - description: The server type infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the server type - returned: always - type: int - sample: 1937415 - name: - description: Name of the server type - returned: always - type: str - sample: fsn1 - description: - description: Detail description of the server type - returned: always - type: str - sample: Falkenstein DC Park 1 - cores: - description: Number of cpu cores a server of this type will have - returned: always - type: int - sample: 1 - memory: - description: Memory a server of this type will have in GB - returned: always - type: int - sample: 1 - disk: - description: Disk size a server of this type will have in GB - returned: always - type: int - sample: 25 - storage_type: - description: Type of server boot drive - returned: always - type: str - sample: local - cpu_type: - description: Type of cpu - returned: always - type: str - sample: shared - architecture: - description: Architecture of cpu - returned: always - type: str - sample: x86 -""" - -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 - - -class AnsibleHcloudServerTypeInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_server_type_info") - self.hcloud_server_type_info = None - - def _prepare_result(self): - tmp = [] - - for server_type in self.hcloud_server_type_info: - if server_type is not None: - tmp.append({ - "id": to_native(server_type.id), - "name": to_native(server_type.name), - "description": to_native(server_type.description), - "cores": server_type.cores, - "memory": server_type.memory, - "disk": server_type.disk, - "storage_type": to_native(server_type.storage_type), - "cpu_type": to_native(server_type.cpu_type), - "architecture": to_native(server_type.architecture) - }) - return tmp - - def get_server_types(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_server_type_info = [self.client.server_types.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_server_type_info = [self.client.server_types.get_by_name( - self.module.params.get("name") - )] - else: - self.hcloud_server_type_info = self.client.server_types.get_all() - - except Exception 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 = AnsibleHcloudServerTypeInfo.define_module() - - is_old_facts = module._name == 'hcloud_server_type_facts' - if is_old_facts: - module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudServerTypeInfo(module) - hcloud.get_server_types() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_server_type_info': result['hcloud_server_type_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_server_type_info': result['hcloud_server_type_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py deleted file mode 100644 index 59a5197f5..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/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_ssh_key - -short_description: Create and manage ssh keys on the Hetzner Cloud. - - -description: - - Create, update and manage ssh keys on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Hetzner Cloud ssh_key to manage. - - Only required if no ssh_key I(name) is given - type: int - name: - description: - - The Name of the Hetzner Cloud ssh_key to manage. - - Only required if no ssh_key I(id) is given or a ssh_key does not exist. - type: str - fingerprint: - description: - - The Fingerprint of the Hetzner Cloud ssh_key to manage. - - Only required if no ssh_key I(id) or I(name) is given. - type: str - labels: - description: - - User-defined labels (key-value pairs) - type: dict - public_key: - description: - - The Public Key to add. - - Required if ssh_key does not exist. - type: str - state: - description: - - State of the ssh_key. - default: present - choices: [ absent, present ] - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic ssh_key - hcloud_ssh_key: - name: my-ssh_key - public_key: "ssh-rsa AAAjjk76kgf...Xt" - state: present - -- name: Create a ssh_key with labels - hcloud_ssh_key: - name: my-ssh_key - public_key: "ssh-rsa AAAjjk76kgf...Xt" - labels: - key: value - mylabel: 123 - state: present - -- name: Ensure the ssh_key is absent (remove if needed) - hcloud_ssh_key: - name: my-ssh_key - state: absent -""" - -RETURN = """ -hcloud_ssh_key: - description: The ssh_key instance - returned: Always - type: complex - contains: - id: - description: ID of the ssh_key - type: int - returned: Always - sample: 12345 - name: - description: Name of the ssh_key - type: str - returned: Always - sample: my-ssh-key - fingerprint: - description: Fingerprint of the ssh_key - type: str - returned: Always - sample: b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f - public_key: - description: Public key of the ssh_key - type: str - returned: Always - sample: "ssh-rsa AAAjjk76kgf...Xt" - labels: - description: User-defined labels (key-value pairs) - type: dict - returned: Always - sample: - key: value - mylabel: 123 -""" - -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 - - -class AnsibleHcloudSSHKey(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_ssh_key") - self.hcloud_ssh_key = None - - def _prepare_result(self): - return { - "id": to_native(self.hcloud_ssh_key.id), - "name": to_native(self.hcloud_ssh_key.name), - "fingerprint": to_native(self.hcloud_ssh_key.fingerprint), - "public_key": to_native(self.hcloud_ssh_key.public_key), - "labels": self.hcloud_ssh_key.labels, - } - - def _get_ssh_key(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_ssh_key = self.client.ssh_keys.get_by_id( - self.module.params.get("id") - ) - elif self.module.params.get("fingerprint") is not None: - self.hcloud_ssh_key = self.client.ssh_keys.get_by_fingerprint( - self.module.params.get("fingerprint") - ) - elif self.module.params.get("name") is not None: - self.hcloud_ssh_key = self.client.ssh_keys.get_by_name( - self.module.params.get("name") - ) - - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_ssh_key(self): - self.module.fail_on_missing_params( - required_params=["name", "public_key"] - ) - params = { - "name": self.module.params.get("name"), - "public_key": self.module.params.get("public_key"), - "labels": self.module.params.get("labels") - } - - if not self.module.check_mode: - try: - self.client.ssh_keys.create(**params) - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_ssh_key() - - def _update_ssh_key(self): - name = self.module.params.get("name") - if name is not None and self.hcloud_ssh_key.name != name: - self.module.fail_on_missing_params( - required_params=["id"] - ) - if not self.module.check_mode: - self.hcloud_ssh_key.update(name=name) - self._mark_as_changed() - - labels = self.module.params.get("labels") - if labels is not None and self.hcloud_ssh_key.labels != labels: - if not self.module.check_mode: - self.hcloud_ssh_key.update(labels=labels) - self._mark_as_changed() - - self._get_ssh_key() - - def present_ssh_key(self): - self._get_ssh_key() - if self.hcloud_ssh_key is None: - self._create_ssh_key() - else: - self._update_ssh_key() - - def delete_ssh_key(self): - self._get_ssh_key() - if self.hcloud_ssh_key is not None: - if not self.module.check_mode: - try: - self.client.ssh_keys.delete(self.hcloud_ssh_key) - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_ssh_key = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - public_key={"type": "str"}, - fingerprint={"type": "str"}, - labels={"type": "dict"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name', 'fingerprint']], - required_if=[['state', 'present', ['name']]], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudSSHKey.define_module() - - hcloud = AnsibleHcloudSSHKey(module) - state = module.params.get("state") - if state == "absent": - hcloud.delete_ssh_key() - elif state == "present": - hcloud.present_ssh_key() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py deleted file mode 100644 index aab98ed60..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/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_ssh_key_info -short_description: Gather infos about your Hetzner Cloud ssh_keys. -description: - - Gather facts about your Hetzner Cloud ssh_keys. - - This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts). - Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)! -author: - - Christopher Schmitt (@cschmitt-hcloud) -options: - id: - description: - - The ID of the ssh key you want to get. - type: int - name: - description: - - The name of the ssh key you want to get. - type: str - fingerprint: - description: - - The fingerprint of the ssh key you want to get. - type: str - label_selector: - description: - - The label selector for the ssh key you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud sshkey infos - hcloud_ssh_key_info: - register: output -- name: Print the gathered infos - debug: - var: output.hcloud_ssh_key_info -""" - -RETURN = """ -hcloud_ssh_key_info: - description: The ssh key instances - returned: Always - type: complex - contains: - id: - description: Numeric identifier of the ssh_key - returned: always - type: int - sample: 1937415 - name: - description: Name of the ssh_key - returned: always - type: str - sample: my-ssh-key - fingerprint: - description: Fingerprint of the ssh key - returned: always - type: str - sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3 - public_key: - description: The actual public key - returned: always - type: str - sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com" - 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 - - -class AnsibleHcloudSSHKeyInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_ssh_key_info") - self.hcloud_ssh_key_info = None - - def _prepare_result(self): - ssh_keys = [] - - for ssh_key in self.hcloud_ssh_key_info: - if ssh_key: - ssh_keys.append({ - "id": to_native(ssh_key.id), - "name": to_native(ssh_key.name), - "fingerprint": to_native(ssh_key.fingerprint), - "public_key": to_native(ssh_key.public_key), - "labels": ssh_key.labels - }) - return ssh_keys - - def get_ssh_keys(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("fingerprint") is not None: - self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint( - self.module.params.get("fingerprint") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_ssh_key_info = self.client.ssh_keys.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_ssh_key_info = self.client.ssh_keys.get_all() - - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - fingerprint={"type": "str"}, - label_selector={"type": "str"}, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudSSHKeyInfo.define_module() - - is_old_facts = module._name == 'hcloud_ssh_key_facts' - if is_old_facts: - module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudSSHKeyInfo(module) - hcloud.get_ssh_keys() - result = hcloud.get_result() - - if is_old_facts: - ansible_info = { - 'hcloud_ssh_key_facts': result['hcloud_ssh_key_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_ssh_key_info': result['hcloud_ssh_key_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py deleted file mode 100644 index aab98ed60..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/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_ssh_key_info -short_description: Gather infos about your Hetzner Cloud ssh_keys. -description: - - Gather facts about your Hetzner Cloud ssh_keys. - - This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts). - Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)! -author: - - Christopher Schmitt (@cschmitt-hcloud) -options: - id: - description: - - The ID of the ssh key you want to get. - type: int - name: - description: - - The name of the ssh key you want to get. - type: str - fingerprint: - description: - - The fingerprint of the ssh key you want to get. - type: str - label_selector: - description: - - The label selector for the ssh key you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud sshkey infos - hcloud_ssh_key_info: - register: output -- name: Print the gathered infos - debug: - var: output.hcloud_ssh_key_info -""" - -RETURN = """ -hcloud_ssh_key_info: - description: The ssh key instances - returned: Always - type: complex - contains: - id: - description: Numeric identifier of the ssh_key - returned: always - type: int - sample: 1937415 - name: - description: Name of the ssh_key - returned: always - type: str - sample: my-ssh-key - fingerprint: - description: Fingerprint of the ssh key - returned: always - type: str - sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3 - public_key: - description: The actual public key - returned: always - type: str - sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com" - 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 - - -class AnsibleHcloudSSHKeyInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_ssh_key_info") - self.hcloud_ssh_key_info = None - - def _prepare_result(self): - ssh_keys = [] - - for ssh_key in self.hcloud_ssh_key_info: - if ssh_key: - ssh_keys.append({ - "id": to_native(ssh_key.id), - "name": to_native(ssh_key.name), - "fingerprint": to_native(ssh_key.fingerprint), - "public_key": to_native(ssh_key.public_key), - "labels": ssh_key.labels - }) - return ssh_keys - - def get_ssh_keys(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("fingerprint") is not None: - self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint( - self.module.params.get("fingerprint") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_ssh_key_info = self.client.ssh_keys.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_ssh_key_info = self.client.ssh_keys.get_all() - - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - fingerprint={"type": "str"}, - label_selector={"type": "str"}, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudSSHKeyInfo.define_module() - - is_old_facts = module._name == 'hcloud_ssh_key_facts' - if is_old_facts: - module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudSSHKeyInfo(module) - hcloud.get_ssh_keys() - result = hcloud.get_result() - - if is_old_facts: - ansible_info = { - 'hcloud_ssh_key_facts': result['hcloud_ssh_key_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_ssh_key_info': result['hcloud_ssh_key_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py deleted file mode 100644 index c2ba66d80..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/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_subnetwork - -short_description: Manage cloud subnetworks on the Hetzner Cloud. - - -description: - - Create, update and delete cloud subnetworks on the Hetzner Cloud. - -author: - - Lukas Kaemmerling (@lkaemmerling) - -options: - network: - description: - - The ID or Name of the Hetzner Cloud Networks. - type: str - required: true - ip_range: - description: - - IP range of the subnetwork. - type: str - required: true - type: - description: - - Type of subnetwork. - type: str - choices: [ server, cloud, vswitch ] - required: true - network_zone: - description: - - Name of network zone. - type: str - required: true - vswitch_id: - description: - - ID of the vSwitch you want to couple with your Network. - - Required if type == vswitch - type: int - state: - description: - - State of the subnetwork. - default: present - choices: [ absent, present ] - type: str - -requirements: - - hcloud-python >= 1.10.0 - -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a basic subnetwork - hcloud_subnetwork: - network: my-network - ip_range: 10.0.0.0/16 - network_zone: eu-central - type: cloud - state: present - -- name: Create a basic subnetwork - hcloud_subnetwork: - network: my-vswitch-network - ip_range: 10.0.0.0/24 - network_zone: eu-central - type: vswitch - vswitch_id: 123 - state: present - -- name: Ensure the subnetwork is absent (remove if needed) - hcloud_subnetwork: - network: my-network - ip_range: 10.0.0.0/8 - network_zone: eu-central - type: cloud - state: absent -""" - -RETURN = """ -hcloud_subnetwork: - description: One Subnet of a Network - returned: always - type: complex - contains: - network: - description: Name of the Network - type: str - returned: always - sample: my-network - ip_range: - description: IP range of the Network - type: str - returned: always - sample: 10.0.0.0/8 - type: - description: Type of subnetwork - type: str - returned: always - sample: server - network_zone: - description: Name of network zone - type: str - returned: always - sample: eu-central - vswitch_id: - description: ID of the vswitch, null if not type vswitch - type: int - returned: always - sample: 123 - gateway: - description: Gateway of the subnetwork - type: str - returned: always - sample: 10.0.0.1 -""" - -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.networks.domain import NetworkSubnet -except ImportError: - NetworkSubnet = None - - -class AnsibleHcloudSubnetwork(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_subnetwork") - self.hcloud_network = None - self.hcloud_subnetwork = None - - def _prepare_result(self): - return { - "network": to_native(self.hcloud_network.name), - "ip_range": to_native(self.hcloud_subnetwork.ip_range), - "type": to_native(self.hcloud_subnetwork.type), - "network_zone": to_native(self.hcloud_subnetwork.network_zone), - "gateway": self.hcloud_subnetwork.gateway, - "vswitch_id": self.hcloud_subnetwork.vswitch_id, - } - - def _get_network(self): - try: - self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) - self.hcloud_subnetwork = None - except Exception as e: - self.module.fail_json(msg=e.message) - - def _get_subnetwork(self): - subnet_ip_range = self.module.params.get("ip_range") - for subnetwork in self.hcloud_network.subnets: - if subnetwork.ip_range == subnet_ip_range: - self.hcloud_subnetwork = subnetwork - - def _create_subnetwork(self): - params = { - "ip_range": self.module.params.get("ip_range"), - "type": self.module.params.get('type'), - "network_zone": self.module.params.get('network_zone') - } - if self.module.params.get('type') == NetworkSubnet.TYPE_VSWITCH: - self.module.fail_on_missing_params( - required_params=["vswitch_id"] - ) - params["vswitch_id"] = self.module.params.get('vswitch_id') - - if not self.module.check_mode: - try: - self.hcloud_network.add_subnet(subnet=NetworkSubnet(**params)).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - - self._mark_as_changed() - self._get_network() - self._get_subnetwork() - - def present_subnetwork(self): - self._get_network() - self._get_subnetwork() - if self.hcloud_subnetwork is None: - self._create_subnetwork() - - def delete_subnetwork(self): - self._get_network() - self._get_subnetwork() - if self.hcloud_subnetwork is not None and self.hcloud_network is not None: - if not self.module.check_mode: - try: - self.hcloud_network.delete_subnet(self.hcloud_subnetwork).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self.hcloud_subnetwork = None - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - network={"type": "str", "required": True}, - network_zone={"type": "str", "required": True}, - type={ - "type": "str", - "required": True, - "choices": ["server", "cloud", "vswitch"] - }, - ip_range={"type": "str", "required": True}, - vswitch_id={"type": "int"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudSubnetwork.define_module() - - hcloud = AnsibleHcloudSubnetwork(module) - state = module.params["state"] - if state == "absent": - hcloud.delete_subnetwork() - elif state == "present": - hcloud.present_subnetwork() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py deleted file mode 100644 index 623a399b4..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/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_volume - -short_description: Create and manage block Volume on the Hetzner Cloud. - - -description: - - Create, update and attach/detach block Volume on the Hetzner Cloud. - -author: - - Christopher Schmitt (@cschmitt-hcloud) - -options: - id: - description: - - The ID of the Hetzner Cloud Block Volume to manage. - - Only required if no volume I(name) is given - type: int - name: - description: - - The Name of the Hetzner Cloud Block Volume to manage. - - Only required if no volume I(id) is given or a volume does not exist. - type: str - size: - description: - - The size of the Block Volume in GB. - - Required if volume does not yet exists. - type: int - automount: - description: - - Automatically mount the Volume. - type: bool - default: False - format: - description: - - Automatically Format the volume on creation - - Can only be used in case the Volume does not exist. - type: str - choices: [xfs, ext4] - location: - description: - - Location of the Hetzner Cloud Volume. - - Required if no I(server) is given and Volume does not exist. - type: str - server: - description: - - Server Name the Volume should be assigned to. - - Required if no I(location) is given and Volume does not exist. - type: str - delete_protection: - description: - - Protect the Volume for deletion. - type: bool - labels: - description: - - User-defined key-value pairs. - type: dict - state: - description: - - State of the Volume. - default: present - choices: [absent, present] - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Create a Volume - hcloud_volume: - name: my-volume - location: fsn1 - size: 100 - state: present -- name: Create a Volume and format it with ext4 - hcloud_volume: - name: my-volume - location: fsn - format: ext4 - size: 100 - state: present -- name: Mount a existing Volume and automount - hcloud_volume: - name: my-volume - server: my-server - automount: yes - state: present -- name: Mount a existing Volume and automount - hcloud_volume: - name: my-volume - server: my-server - automount: yes - state: present -- name: Ensure the Volume is absent (remove if needed) - hcloud_volume: - name: my-volume - state: absent -""" - -RETURN = """ -hcloud_volume: - description: The block Volume - returned: Always - type: complex - contains: - id: - description: ID of the Volume - type: int - returned: Always - sample: 12345 - name: - description: Name of the Volume - type: str - returned: Always - sample: my-volume - size: - description: Size in GB of the Volume - type: int - returned: Always - sample: 1337 - linux_device: - description: Path to the device that contains the Volume. - returned: always - type: str - sample: /dev/disk/by-id/scsi-0HC_Volume_12345 - version_added: "0.1.0" - location: - description: Location name where the Volume is located at - type: str - returned: Always - sample: "fsn1" - labels: - description: User-defined labels (key-value pairs) - type: dict - returned: Always - sample: - key: value - mylabel: 123 - server: - description: Server name where the Volume is attached to - type: str - returned: Always - sample: "my-server" - delete_protection: - description: True if Volume is protected for deletion - type: bool - returned: always - sample: false - version_added: "0.1.0" -""" - -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 - - -class AnsibleHcloudVolume(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_volume") - self.hcloud_volume = None - - def _prepare_result(self): - server_name = None - if self.hcloud_volume.server is not None: - server_name = to_native(self.hcloud_volume.server.name) - - return { - "id": to_native(self.hcloud_volume.id), - "name": to_native(self.hcloud_volume.name), - "size": self.hcloud_volume.size, - "location": to_native(self.hcloud_volume.location.name), - "labels": self.hcloud_volume.labels, - "server": server_name, - "linux_device": to_native(self.hcloud_volume.linux_device), - "delete_protection": self.hcloud_volume.protection["delete"], - } - - def _get_volume(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_volume = self.client.volumes.get_by_id( - self.module.params.get("id") - ) - else: - self.hcloud_volume = self.client.volumes.get_by_name( - self.module.params.get("name") - ) - except Exception as e: - self.module.fail_json(msg=e.message) - - def _create_volume(self): - self.module.fail_on_missing_params( - required_params=["name", "size"] - ) - params = { - "name": self.module.params.get("name"), - "size": self.module.params.get("size"), - "automount": self.module.params.get("automount"), - "format": self.module.params.get("format"), - "labels": self.module.params.get("labels") - } - if self.module.params.get("server") is not None: - params['server'] = self.client.servers.get_by_name(self.module.params.get("server")) - elif self.module.params.get("location") is not None: - params['location'] = self.client.locations.get_by_name(self.module.params.get("location")) - else: - self.module.fail_json(msg="server or location is required") - - if not self.module.check_mode: - try: - resp = self.client.volumes.create(**params) - resp.action.wait_until_finished() - [action.wait_until_finished() for action in resp.next_actions] - delete_protection = self.module.params.get("delete_protection") - if delete_protection is not None: - self._get_volume() - self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() - except Exception as e: - self.module.fail_json(msg=e.message) - self._mark_as_changed() - self._get_volume() - - def _update_volume(self): - try: - size = self.module.params.get("size") - if size: - if self.hcloud_volume.size < size: - if not self.module.check_mode: - self.hcloud_volume.resize(size).wait_until_finished() - self._mark_as_changed() - elif self.hcloud_volume.size > size: - self.module.warn("Shrinking of volumes is not supported") - - server_name = self.module.params.get("server") - if server_name: - server = self.client.servers.get_by_name(server_name) - if self.hcloud_volume.server is None or self.hcloud_volume.server.name != server.name: - if not self.module.check_mode: - automount = self.module.params.get("automount", False) - self.hcloud_volume.attach(server, automount=automount).wait_until_finished() - self._mark_as_changed() - else: - if self.hcloud_volume.server is not None: - if not self.module.check_mode: - self.hcloud_volume.detach().wait_until_finished() - self._mark_as_changed() - - labels = self.module.params.get("labels") - if labels is not None and labels != self.hcloud_volume.labels: - if not self.module.check_mode: - self.hcloud_volume.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_volume.protection["delete"]: - if not self.module.check_mode: - self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() - self._mark_as_changed() - - self._get_volume() - except Exception as e: - self.module.fail_json(msg=e.message) - - def present_volume(self): - self._get_volume() - if self.hcloud_volume is None: - self._create_volume() - else: - self._update_volume() - - def delete_volume(self): - try: - self._get_volume() - if self.hcloud_volume is not None: - if not self.module.check_mode: - if self.hcloud_volume.server is not None: - self.hcloud_volume.detach().wait_until_finished() - self.client.volumes.delete(self.hcloud_volume) - self._mark_as_changed() - self.hcloud_volume = None - except Exception as e: - self.module.fail_json(msg=e.message) - - @staticmethod - def define_module(): - return AnsibleModule( - argument_spec=dict( - id={"type": "int"}, - name={"type": "str"}, - size={"type": "int"}, - location={"type": "str"}, - server={"type": "str"}, - labels={"type": "dict"}, - automount={"type": "bool", "default": False}, - format={"type": "str", - "choices": ['xfs', 'ext4'], - }, - delete_protection={"type": "bool"}, - state={ - "choices": ["absent", "present"], - "default": "present", - }, - **Hcloud.base_module_arguments() - ), - required_one_of=[['id', 'name']], - mutually_exclusive=[["location", "server"]], - supports_check_mode=True, - ) - - -def main(): - module = AnsibleHcloudVolume.define_module() - - hcloud = AnsibleHcloudVolume(module) - state = module.params.get("state") - if state == "absent": - module.fail_on_missing_params( - required_params=["name"] - ) - hcloud.delete_volume() - else: - hcloud.present_volume() - - module.exit_json(**hcloud.get_result()) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py deleted file mode 100644 index 9520bfa14..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/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_volume_info - -short_description: Gather infos about your Hetzner Cloud Volumes. - -description: - - Gather infos about your Hetzner Cloud Volumes. - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Volume you want to get. - type: int - name: - description: - - The name of the Volume you want to get. - type: str - label_selector: - description: - - The label selector for the Volume you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud Volume infos - hcloud_volume_info: - register: output -- name: Print the gathered infos - debug: - var: output.hcloud_volume_info -""" - -RETURN = """ -hcloud_volume_info: - description: The Volume infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the Volume - returned: always - type: int - sample: 1937415 - name: - description: Name of the Volume - returned: always - type: str - sample: my-volume - size: - description: Size of the Volume - returned: always - type: str - sample: 10 - linux_device: - description: Path to the device that contains the Volume. - returned: always - type: str - sample: /dev/disk/by-id/scsi-0HC_Volume_12345 - version_added: "0.1.0" - location: - description: Name of the location where the Volume resides in - returned: always - type: str - sample: fsn1 - server: - description: Name of the server where the Volume is attached to - returned: always - type: str - sample: my-server - delete_protection: - description: True if the Volume is protected for deletion - returned: always - type: bool - version_added: "0.1.0" - 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 - - -class AnsibleHcloudVolumeInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_volume_info") - self.hcloud_volume_info = None - - def _prepare_result(self): - tmp = [] - - for volume in self.hcloud_volume_info: - if volume is not None: - server_name = None - if volume.server is not None: - server_name = to_native(volume.server.name) - tmp.append({ - "id": to_native(volume.id), - "name": to_native(volume.name), - "size": volume.size, - "location": to_native(volume.location.name), - "labels": volume.labels, - "server": server_name, - "linux_device": to_native(volume.linux_device), - "delete_protection": volume.protection["delete"], - }) - - return tmp - - def get_volumes(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_volume_info = [self.client.volumes.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_volume_info = [self.client.volumes.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_volume_info = self.client.volumes.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_volume_info = self.client.volumes.get_all() - - except Exception 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 = AnsibleHcloudVolumeInfo.define_module() - - is_old_facts = module._name == 'hcloud_volume_facts' - if is_old_facts: - module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudVolumeInfo(module) - - hcloud.get_volumes() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_volume_facts': result['hcloud_volume_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_volume_info': result['hcloud_volume_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py deleted file mode 100644 index 9520bfa14..000000000 --- a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/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_volume_info - -short_description: Gather infos about your Hetzner Cloud Volumes. - -description: - - Gather infos about your Hetzner Cloud Volumes. - -author: - - Lukas Kaemmerling (@LKaemmerling) - -options: - id: - description: - - The ID of the Volume you want to get. - type: int - name: - description: - - The name of the Volume you want to get. - type: str - label_selector: - description: - - The label selector for the Volume you want to get. - type: str -extends_documentation_fragment: -- hetzner.hcloud.hcloud - -''' - -EXAMPLES = """ -- name: Gather hcloud Volume infos - hcloud_volume_info: - register: output -- name: Print the gathered infos - debug: - var: output.hcloud_volume_info -""" - -RETURN = """ -hcloud_volume_info: - description: The Volume infos as list - returned: always - type: complex - contains: - id: - description: Numeric identifier of the Volume - returned: always - type: int - sample: 1937415 - name: - description: Name of the Volume - returned: always - type: str - sample: my-volume - size: - description: Size of the Volume - returned: always - type: str - sample: 10 - linux_device: - description: Path to the device that contains the Volume. - returned: always - type: str - sample: /dev/disk/by-id/scsi-0HC_Volume_12345 - version_added: "0.1.0" - location: - description: Name of the location where the Volume resides in - returned: always - type: str - sample: fsn1 - server: - description: Name of the server where the Volume is attached to - returned: always - type: str - sample: my-server - delete_protection: - description: True if the Volume is protected for deletion - returned: always - type: bool - version_added: "0.1.0" - 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 - - -class AnsibleHcloudVolumeInfo(Hcloud): - def __init__(self, module): - Hcloud.__init__(self, module, "hcloud_volume_info") - self.hcloud_volume_info = None - - def _prepare_result(self): - tmp = [] - - for volume in self.hcloud_volume_info: - if volume is not None: - server_name = None - if volume.server is not None: - server_name = to_native(volume.server.name) - tmp.append({ - "id": to_native(volume.id), - "name": to_native(volume.name), - "size": volume.size, - "location": to_native(volume.location.name), - "labels": volume.labels, - "server": server_name, - "linux_device": to_native(volume.linux_device), - "delete_protection": volume.protection["delete"], - }) - - return tmp - - def get_volumes(self): - try: - if self.module.params.get("id") is not None: - self.hcloud_volume_info = [self.client.volumes.get_by_id( - self.module.params.get("id") - )] - elif self.module.params.get("name") is not None: - self.hcloud_volume_info = [self.client.volumes.get_by_name( - self.module.params.get("name") - )] - elif self.module.params.get("label_selector") is not None: - self.hcloud_volume_info = self.client.volumes.get_all( - label_selector=self.module.params.get("label_selector")) - else: - self.hcloud_volume_info = self.client.volumes.get_all() - - except Exception 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 = AnsibleHcloudVolumeInfo.define_module() - - is_old_facts = module._name == 'hcloud_volume_facts' - if is_old_facts: - module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', " - "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") - - hcloud = AnsibleHcloudVolumeInfo(module) - - hcloud.get_volumes() - result = hcloud.get_result() - if is_old_facts: - ansible_info = { - 'hcloud_volume_facts': result['hcloud_volume_info'] - } - module.exit_json(ansible_facts=ansible_info) - else: - ansible_info = { - 'hcloud_volume_info': result['hcloud_volume_info'] - } - module.exit_json(**ansible_info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/image_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/image_info.py new file mode 100644 index 000000000..b0d7fc482 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/image_info.py @@ -0,0 +1,209 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: image_info + +short_description: Gather infos about your Hetzner Cloud images. + + +description: + - Gather infos about your Hetzner Cloud images. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the image you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the image you want to get. + type: str + label_selector: + description: + - The label selector for the images you want to get. + type: str + type: + description: + - The type for the images you want to get. + default: system + choices: [ system, snapshot, backup ] + type: str + architecture: + description: + - The architecture for the images you want to get. + type: str + choices: [ x86, arm ] +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud image infos + hetzner.hcloud.image_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_image_info: + description: The image infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the image + returned: always + type: int + sample: 1937415 + type: + description: Type of the image + returned: always + type: str + sample: system + status: + description: Status of the image + returned: always + type: str + sample: available + name: + description: Name of the image + returned: always + type: str + sample: ubuntu-22.04 + description: + description: Detail description of the image + returned: always + type: str + sample: Ubuntu 18.04 Standard 64 bit + os_flavor: + description: OS flavor of the image + returned: always + type: str + sample: ubuntu + os_version: + description: OS version of the image + returned: always + type: str + sample: 18.04 + architecture: + description: Image is compatible with this architecture + returned: always + type: str + sample: x86 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.images import BoundImage + + +class AnsibleHCloudImageInfo(AnsibleHCloud): + represent = "hcloud_image_info" + + hcloud_image_info: list[BoundImage] | None = None + + def _prepare_result(self): + tmp = [] + + for image in self.hcloud_image_info: + if image is not None: + tmp.append( + { + "id": to_native(image.id), + "status": to_native(image.status), + "type": to_native(image.type), + "name": to_native(image.name), + "description": to_native(image.description), + "os_flavor": to_native(image.os_flavor), + "os_version": to_native(image.os_version), + "architecture": to_native(image.architecture), + "labels": image.labels, + } + ) + return tmp + + def get_images(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_image_info = [self.client.images.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None: + self.hcloud_image_info = [ + self.client.images.get_by_name_and_architecture( + self.module.params.get("name"), + self.module.params.get("architecture"), + ) + ] + elif self.module.params.get("name") is not None: + self.module.warn( + "This module only returns x86 images by default. Please set architecture:x86|arm to hide this message." + ) + self.hcloud_image_info = [self.client.images.get_by_name(self.module.params.get("name"))] + else: + params = {} + label_selector = self.module.params.get("label_selector") + if label_selector: + params["label_selector"] = label_selector + + image_type = self.module.params.get("type") + if image_type: + params["type"] = image_type + + architecture = self.module.params.get("architecture") + if architecture: + params["architecture"] = architecture + + self.hcloud_image_info = self.client.images.get_all(**params) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"}, + architecture={"choices": ["x86", "arm"], "type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudImageInfo.define_module() + hcloud = AnsibleHCloudImageInfo(module) + + hcloud.get_images() + result = hcloud.get_result() + + ansible_info = {"hcloud_image_info": result["hcloud_image_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/iso_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/iso_info.py new file mode 100644 index 000000000..e623d1714 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/iso_info.py @@ -0,0 +1,206 @@ +#!/usr/bin/python + +# Copyright: (c) 2022, Patrice Le Guyader +# heavily inspired by the work of @LKaemmerling +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: iso_info + +short_description: Gather infos about the Hetzner Cloud ISO list. + +description: + - Gather infos about the Hetzner Cloud ISO list. + +author: + - Patrice Le Guyader (@patlegu) + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the ISO image you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the ISO you want to get. + type: str + architecture: + description: + - Filter ISOs with compatible architecture. + type: str + choices: [x86, arm] + include_architecture_wildcard: + description: + - Include ISOs with wildcard architecture (architecture is null). + - Works only if architecture filter is specified. + type: bool + +extends_documentation_fragment: + - hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Gather hcloud ISO type infos + hetzner.hcloud.iso_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_iso_info +""" + +RETURN = """ +hcloud_iso_info: + description: The ISO type infos as list + returned: always + type: complex + contains: + id: + description: ID of the ISO + returned: always + type: int + sample: 22110 + name: + description: Unique identifier of the ISO. Only set for public ISOs + returned: always + type: str + sample: debian-12.0.0-amd64-netinst.iso + description: + description: Description of the ISO + returned: always + type: str + sample: Debian 12.0 (amd64/netinstall) + architecture: + description: > + Type of cpu architecture this ISO is compatible with. + None indicates no restriction on the architecture (wildcard). + returned: when supported + type: str + sample: x86 + type: + description: Type of the ISO, can be one of `public`, `private`. + returned: always + type: str + sample: public + deprecated: + description: > + ISO 8601 timestamp of deprecation, None if ISO is still available. + After the deprecation time it will no longer be possible to attach the + ISO to servers. This field is deprecated. Use `deprecation` instead. + returned: always + type: str + sample: "2024-12-01T00:00:00+00:00" + deprecation: + description: > + Describes if, when & how the resources was deprecated. If this field is + set to None the resource is not deprecated. If it has a value, it is + considered deprecated. + returned: if the resource is deprecated + type: dict + contains: + announced: + description: Date of when the deprecation was announced. + returned: always + type: str + sample: "2021-11-01T00:00:00+00:00" + unavailable_after: + description: > + After the time in this field, the resource will not be available + from the general listing endpoint of the resource type, and it can + not be used in new resources. For example, if this is an image, + you can not create new servers with this image after the mentioned + date. + returned: always + type: str + sample: "2021-12-01T00:00:00+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.isos import BoundIso + + +class AnsibleHCloudIsoInfo(AnsibleHCloud): + represent = "hcloud_iso_info" + + hcloud_iso_info: list[BoundIso] | None = None + + def _prepare_result(self): + tmp = [] + + for iso_info in self.hcloud_iso_info: + if iso_info is None: + continue + + tmp.append( + { + "id": to_native(iso_info.id), + "name": to_native(iso_info.name), + "description": to_native(iso_info.description), + "type": iso_info.type, + "architecture": iso_info.architecture, + "deprecated": ( + iso_info.deprecation.unavailable_after if iso_info.deprecation is not None else None + ), + "deprecation": ( + { + "announced": iso_info.deprecation.announced.isoformat(), + "unavailable_after": iso_info.deprecation.unavailable_after.isoformat(), + } + if iso_info.deprecation is not None + else None + ), + } + ) + + return tmp + + def get_iso_infos(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_iso_info = [self.client.isos.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_iso_info = [self.client.isos.get_by_name(self.module.params.get("name"))] + else: + self.hcloud_iso_info = self.client.isos.get_all( + architecture=self.module.params.get("architecture"), + include_wildcard_architecture=self.module.params.get("include_wildcard_architecture"), + ) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + architecture={"type": "str", "choices": ["x86", "arm"]}, + include_architecture_wildcard={"type": "bool"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudIsoInfo.define_module() + hcloud = AnsibleHCloudIsoInfo(module) + hcloud.get_iso_infos() + result = hcloud.get_result() + ansible_info = {"hcloud_iso_info": result["hcloud_iso_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer.py b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer.py new file mode 100644 index 000000000..1a0d8712a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer.py @@ -0,0 +1,340 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: 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 exist. + type: str + load_balancer_type: + description: + - The Load Balancer Type of the Hetzner Cloud Load Balancer to manage. + - Required if Load Balancer does not exist. + type: str + algorithm: + description: + - Algorithm of the Load Balancer. + type: str + default: round_robin + choices: [round_robin, least_connections] + location: + description: + - Location of Load Balancer. + - Required if no I(network_zone) is given and Load Balancer does not exist. + type: str + network_zone: + description: + - Network Zone of Load Balancer. + - Required of no I(location) is given and Load Balancer does not exist. + 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 +""" + +EXAMPLES = """ +- name: Create a basic Load Balancer + hetzner.hcloud.load_balancer: + name: my-Load Balancer + load_balancer_type: lb11 + algorithm: round_robin + location: fsn1 + state: present + +- name: Ensure the Load Balancer is absent (remove if needed) + hetzner.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 + algorithm: + description: Algorithm of the Load Balancer. + returned: always + type: str + choices: [round_robin, least_connections] + sample: round_robin + 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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.load_balancers import ( + BoundLoadBalancer, + LoadBalancerAlgorithm, +) + + +class AnsibleHCloudLoadBalancer(AnsibleHCloud): + represent = "hcloud_load_balancer" + + hcloud_load_balancer: BoundLoadBalancer | None = 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), + "algorithm": to_native(self.hcloud_load_balancer.algorithm.type), + "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": False if self.hcloud_load_balancer.public_net.enabled else True, + } + + 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 HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_load_balancer(self): + self.module.fail_on_missing_params(required_params=["name", "load_balancer_type"]) + try: + params = { + "name": self.module.params.get("name"), + "algorithm": LoadBalancerAlgorithm(type=self.module.params.get("algorithm", "round_robin")), + "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) + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_load_balancer() + self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_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 != ( + not 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() + + load_balancer_type = self.module.params.get("load_balancer_type") + if ( + load_balancer_type is not None + and self.hcloud_load_balancer.load_balancer_type.name != load_balancer_type + ): + new_load_balancer_type = self.client.load_balancer_types.get_by_name(load_balancer_type) + if not new_load_balancer_type: + self.module.fail_json(msg="unknown load balancer type") + if not self.module.check_mode: + self.hcloud_load_balancer.change_type( + load_balancer_type=new_load_balancer_type, + ).wait_until_finished(max_retries=1000) + + self._mark_as_changed() + + algorithm = self.module.params.get("algorithm") + if algorithm is not None and self.hcloud_load_balancer.algorithm.type != algorithm: + self.hcloud_load_balancer.change_algorithm( + algorithm=LoadBalancerAlgorithm(type=algorithm) + ).wait_until_finished() + self._mark_as_changed() + + self._get_load_balancer() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + 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 HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + load_balancer_type={"type": "str"}, + algorithm={"choices": ["round_robin", "least_connections"], "default": "round_robin"}, + 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", + }, + **super().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/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_info.py new file mode 100644 index 000000000..19ead98c2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_info.py @@ -0,0 +1,424 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: load_balancer_info + +short_description: Gather infos about your Hetzner Cloud Load Balancers. + + +description: + - Gather infos about your Hetzner Cloud Load Balancers.. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Load Balancers you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the Load Balancers you want to get. + type: str + label_selector: + description: + - The label selector for the Load Balancers you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud load_balancer infos + hetzner.hcloud.load_balancer_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_load_balancer_info: + description: The load_balancer infos as list + 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 + targets: + description: The targets of the Load Balancer + 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 + label_selector: + description: Label Selector + type: str + returned: if I(type) is label_selector + sample: application=backend + ip: + description: IP of the dedicated server + type: str + returned: if I(type) is ip + sample: 127.0.0.1 + 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.load_balancer_network) + type: bool + sample: true + returned: always + health_status: + description: + - List of health statuses of the services on this target. Only present for target types "server" and "ip". + type: list + returned: if I(type) is server or ip + contains: + listen_port: + description: Load Balancer Target listen port + type: int + returned: always + sample: 80 + status: + description: Load Balancer Target status + type: str + choices: [healthy, unhealthy, unknown] + returned: always + sample: healthy + services: + description: all services from this Load Balancer + returned: Always + type: complex + contains: + 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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.load_balancers import BoundLoadBalancer + + +class AnsibleHCloudLoadBalancerInfo(AnsibleHCloud): + represent = "hcloud_load_balancer_info" + + hcloud_load_balancer_info: list[BoundLoadBalancer] | None = None + + def _prepare_result(self): + tmp = [] + + for load_balancer in self.hcloud_load_balancer_info: + if load_balancer is not None: + services = [self._prepare_service_result(service) for service in load_balancer.services] + targets = [self._prepare_target_result(target) for target in load_balancer.targets] + + private_ipv4_address = ( + None if len(load_balancer.private_net) == 0 else to_native(load_balancer.private_net[0].ip) + ) + tmp.append( + { + "id": to_native(load_balancer.id), + "name": to_native(load_balancer.name), + "ipv4_address": to_native(load_balancer.public_net.ipv4.ip), + "ipv6_address": to_native(load_balancer.public_net.ipv6.ip), + "private_ipv4_address": private_ipv4_address, + "load_balancer_type": to_native(load_balancer.load_balancer_type.name), + "location": to_native(load_balancer.location.name), + "labels": load_balancer.labels, + "delete_protection": load_balancer.protection["delete"], + "disable_public_interface": False if load_balancer.public_net.enabled else True, + "targets": targets, + "services": services, + } + ) + return tmp + + @staticmethod + def _prepare_service_result(service): + http = None + if service.protocol != "tcp": + http = { + "cookie_name": to_native(service.http.cookie_name), + "cookie_lifetime": service.http.cookie_name, + "redirect_http": service.http.redirect_http, + "sticky_sessions": service.http.sticky_sessions, + "certificates": [to_native(certificate.name) for certificate in service.http.certificates], + } + health_check = { + "protocol": to_native(service.health_check.protocol), + "port": service.health_check.port, + "interval": service.health_check.interval, + "timeout": service.health_check.timeout, + "retries": service.health_check.retries, + } + if service.health_check.protocol != "tcp": + health_check["http"] = { + "domain": to_native(service.health_check.http.domain), + "path": to_native(service.health_check.http.path), + "response": to_native(service.health_check.http.response), + "certificates": [to_native(status_code) for status_code in service.health_check.http.status_codes], + "tls": service.health_check.http.tls, + } + return { + "protocol": to_native(service.protocol), + "listen_port": service.listen_port, + "destination_port": service.destination_port, + "proxyprotocol": service.proxyprotocol, + "http": http, + "health_check": health_check, + } + + @staticmethod + def _prepare_target_result(target): + result = { + "type": to_native(target.type), + "use_private_ip": target.use_private_ip, + } + if target.type == "server": + result["server"] = to_native(target.server.name) + elif target.type == "label_selector": + result["label_selector"] = to_native(target.label_selector.selector) + elif target.type == "ip": + result["ip"] = to_native(target.ip.ip) + + if target.health_status is not None: + result["health_status"] = [ + { + "listen_port": item.listen_port, + "status": item.status, + } + for item in target.health_status + ] + + return result + + def get_load_balancers(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_load_balancer_info = [ + self.client.load_balancers.get_by_name(self.module.params.get("name")) + ] + else: + params = {} + label_selector = self.module.params.get("label_selector") + if label_selector: + params["label_selector"] = label_selector + + self.hcloud_load_balancer_info = self.client.load_balancers.get_all(**params) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudLoadBalancerInfo.define_module() + hcloud = AnsibleHCloudLoadBalancerInfo(module) + + hcloud.get_load_balancers() + result = hcloud.get_result() + + ansible_info = {"hcloud_load_balancer_info": result["hcloud_load_balancer_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_network.py new file mode 100644 index 000000000..4560f8735 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_network.py @@ -0,0 +1,203 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: 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: + - Name or ID of the Hetzner Cloud Networks. + type: str + required: true + load_balancer: + description: + - Name or ID 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 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic Load Balancer network + hetzner.hcloud.load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + state: present + +- name: Create a Load Balancer network and specify the ip address + hetzner.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) + hetzner.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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.load_balancers import BoundLoadBalancer, PrivateNet +from ..module_utils.vendor.hcloud.networks import BoundNetwork + + +class AnsibleHCloudLoadBalancerNetwork(AnsibleHCloud): + represent = "hcloud_load_balancer_network" + + hcloud_network: BoundNetwork | None = None + hcloud_load_balancer: BoundLoadBalancer | None = None + hcloud_load_balancer_network: PrivateNet | None = 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_get_by_name_or_id( + "networks", + self.module.params.get("network"), + ) + self.hcloud_load_balancer = self._client_get_by_name_or_id( + "load_balancers", + self.module.params.get("load_balancer"), + ) + + self.hcloud_load_balancer_network = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _get_load_balancer_network(self): + for private_net in self.hcloud_load_balancer.private_net: + if private_net.network.id == self.hcloud_network.id: + self.hcloud_load_balancer_network = private_net + + 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 HCloudException as exception: + self.fail_json_hcloud(exception) + + 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: + try: + self.hcloud_load_balancer.detach_from_network( + self.hcloud_load_balancer_network.network + ).wait_until_finished() + self._mark_as_changed() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + self.hcloud_load_balancer_network = None + + @classmethod + def define_module(cls): + 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", + }, + **super().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/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_service.py b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_service.py new file mode 100644 index 000000000..1fc18deef --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_service.py @@ -0,0 +1,578 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: 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: + - Name or ID 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 exist and protocol is tcp. + type: int + protocol: + description: + - Protocol of the service. + - Required if Load Balancer does not exist. + type: str + choices: [ http, https, tcp ] + proxyprotocol: + description: + - Enable the PROXY protocol. + type: bool + default: False + 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 + default: False + redirect_http: + description: + - Redirect Traffic from Port 80 to Port 443, only available if protocol is https + type: bool + default: False + 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 + default: False + state: + description: + - State of the Load Balancer. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic Load Balancer service with Port 80 + hetzner.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) + hetzner.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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import APIException, HCloudException +from ..module_utils.vendor.hcloud.load_balancers import ( + BoundLoadBalancer, + LoadBalancerHealtCheckHttp, + LoadBalancerHealthCheck, + LoadBalancerService, + LoadBalancerServiceHttp, +) + + +class AnsibleHCloudLoadBalancerService(AnsibleHCloud): + represent = "hcloud_load_balancer_service" + + hcloud_load_balancer: BoundLoadBalancer | None = None + hcloud_load_balancer_service: LoadBalancerService | None = 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), + "status_codes": [ + 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.http.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_get_by_name_or_id( + "load_balancers", + self.module.params.get("load_balancer"), + ) + self._get_load_balancer_service() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + 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 HCloudException as exception: + self.fail_json_hcloud(exception) + 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 Exception: + hcloud_cert = self.client.certificates.get_by_id(certificate) + except HCloudException as exception: + self.fail_json_hcloud(exception) + service_http.certificates.append(hcloud_cert) + + return service_http + + def __get_service_health_checks(self, health_check): + service_health_check = LoadBalancerHealthCheck() + 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 = LoadBalancerHealtCheckHttp() + 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 HCloudException as exception: + self.fail_json_hcloud(exception) + 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: + try: + self.hcloud_load_balancer.delete_service(self.hcloud_load_balancer_service).wait_until_finished( + max_retries=1000 + ) + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_load_balancer_service = None + except APIException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + 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", + }, + **super().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/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_target.py b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_target.py new file mode 100644 index 000000000..36e7f608f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_target.py @@ -0,0 +1,313 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: 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, label_selector, ip ] + required: true + load_balancer: + description: + - Name or ID of the Hetzner Cloud Load Balancer. + type: str + required: true + server: + description: + - Name or ID of the Hetzner Cloud Server. + - Required if I(type) is server + type: str + label_selector: + description: + - A Label Selector that will be used to determine the targets dynamically + - Required if I(type) is label_selector + type: str + ip: + description: + - An IP from a Hetzner Dedicated Server, needs to belongs to the same user as the project. + - Required if I(type) is ip + 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.load_balancer_network) + type: bool + default: False + state: + description: + - State of the load_balancer_network. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a server Load Balancer target + hetzner.hcloud.load_balancer_target: + type: server + load_balancer: my-LoadBalancer + server: my-server + state: present + +- name: Create a label_selector Load Balancer target + hetzner.hcloud.load_balancer_target: + type: label_selector + load_balancer: my-LoadBalancer + label_selector: application=backend + state: present + +- name: Create an IP Load Balancer target + hetzner.hcloud.load_balancer_target: + type: ip + load_balancer: my-LoadBalancer + ip: 127.0.0.1 + state: present + +- name: Ensure the Load Balancer target is absent (remove if needed) + hetzner.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 + label_selector: + description: Label Selector + type: str + returned: if I(type) is label_selector + sample: application=backend + ip: + description: IP of the dedicated server + type: str + returned: if I(type) is ip + sample: 127.0.0.1 + 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.load_balancer_network) + type: bool + sample: true + returned: always +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import APIException, HCloudException +from ..module_utils.vendor.hcloud.load_balancers import ( + BoundLoadBalancer, + LoadBalancerTarget, + LoadBalancerTargetIP, + LoadBalancerTargetLabelSelector, +) +from ..module_utils.vendor.hcloud.servers import BoundServer + + +class AnsibleHCloudLoadBalancerTarget(AnsibleHCloud): + represent = "hcloud_load_balancer_target" + + hcloud_load_balancer: BoundLoadBalancer | None = None + hcloud_load_balancer_target: LoadBalancerTarget | None = None + hcloud_server: BoundServer | None = None + + def _prepare_result(self): + result = { + "type": to_native(self.hcloud_load_balancer_target.type), + "load_balancer": to_native(self.hcloud_load_balancer.name), + "use_private_ip": self.hcloud_load_balancer_target.use_private_ip, + } + + if self.hcloud_load_balancer_target.type == "server": + result["server"] = to_native(self.hcloud_load_balancer_target.server.name) + elif self.hcloud_load_balancer_target.type == "label_selector": + result["label_selector"] = to_native(self.hcloud_load_balancer_target.label_selector.selector) + elif self.hcloud_load_balancer_target.type == "ip": + result["ip"] = to_native(self.hcloud_load_balancer_target.ip.ip) + return result + + def _get_load_balancer_and_target(self): + try: + self.hcloud_load_balancer = self._client_get_by_name_or_id( + "load_balancers", + self.module.params.get("load_balancer"), + ) + + if self.module.params.get("type") == "server": + self.hcloud_server = self._client_get_by_name_or_id( + "servers", + self.module.params.get("server"), + ) + + self.hcloud_load_balancer_target = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _get_load_balancer_target(self): + for target in self.hcloud_load_balancer.targets: + if self.module.params.get("type") == "server" and target.type == "server": + if target.server.id == self.hcloud_server.id: + self.hcloud_load_balancer_target = target + elif self.module.params.get("type") == "label_selector" and target.type == "label_selector": + if target.label_selector.selector == self.module.params.get("label_selector"): + self.hcloud_load_balancer_target = target + elif self.module.params.get("type") == "ip" and target.type == "ip": + if target.ip.ip == self.module.params.get("ip"): + 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"), + ) + elif self.module.params.get("type") == "label_selector": + self.module.fail_on_missing_params(required_params=["label_selector"]) + params["target"] = LoadBalancerTarget( + type=self.module.params.get("type"), + label_selector=LoadBalancerTargetLabelSelector(selector=self.module.params.get("label_selector")), + use_private_ip=self.module.params.get("use_private_ip"), + ) + elif self.module.params.get("type") == "ip": + self.module.fail_on_missing_params(required_params=["ip"]) + params["target"] = LoadBalancerTarget( + type=self.module.params.get("type"), + ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")), + use_private_ip=False, + ) + + if not self.module.check_mode: + try: + self.hcloud_load_balancer.add_target(**params).wait_until_finished() + except APIException as exception: + if exception.code == "locked" or exception.code == "conflict": + self._create_load_balancer_target() + else: + self.fail_json_hcloud(exception) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + 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": + self.module.fail_on_missing_params(required_params=["server"]) + target = LoadBalancerTarget(type=self.module.params.get("type"), server=self.hcloud_server) + elif self.module.params.get("type") == "label_selector": + self.module.fail_on_missing_params(required_params=["label_selector"]) + target = LoadBalancerTarget( + type=self.module.params.get("type"), + label_selector=LoadBalancerTargetLabelSelector( + selector=self.module.params.get("label_selector") + ), + use_private_ip=self.module.params.get("use_private_ip"), + ) + elif self.module.params.get("type") == "ip": + self.module.fail_on_missing_params(required_params=["ip"]) + target = LoadBalancerTarget( + type=self.module.params.get("type"), + ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")), + use_private_ip=False, + ) + try: + self.hcloud_load_balancer.remove_target(target).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_load_balancer_target = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + type={"type": "str", "required": True, "choices": ["server", "label_selector", "ip"]}, + load_balancer={"type": "str", "required": True}, + server={"type": "str"}, + label_selector={"type": "str"}, + ip={"type": "str"}, + use_private_ip={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().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/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_type_info.py new file mode 100644 index 000000000..67feafd59 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/load_balancer_type_info.py @@ -0,0 +1,161 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: 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. + - The module will fail if the provided ID is invalid. + 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 + hetzner.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.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.load_balancer_types import BoundLoadBalancerType + + +class AnsibleHCloudLoadBalancerTypeInfo(AnsibleHCloud): + represent = "hcloud_load_balancer_type_info" + + hcloud_load_balancer_type_info: list[BoundLoadBalancerType] | None = 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 HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **super().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/ansible_collections/hetzner/hcloud/plugins/modules/location_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/location_info.py new file mode 100644 index 000000000..ac495c6c8 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/location_info.py @@ -0,0 +1,145 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: location_info + +short_description: Gather infos about your Hetzner Cloud locations. + + +description: + - Gather infos about your Hetzner Cloud locations. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the location you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the location you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud location infos + hetzner.hcloud.location_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_location_info: + description: The location infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the location + returned: always + type: int + sample: 1937415 + name: + description: Name of the location + returned: always + type: str + sample: fsn1 + description: + description: Detail description of the location + returned: always + type: str + sample: Falkenstein DC Park 1 + country: + description: Country code of the location + returned: always + type: str + sample: DE + city: + description: City of the location + returned: always + type: str + sample: Falkenstein +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.locations import BoundLocation + + +class AnsibleHCloudLocationInfo(AnsibleHCloud): + represent = "hcloud_location_info" + + hcloud_location_info: list[BoundLocation] | None = None + + def _prepare_result(self): + tmp = [] + + for location in self.hcloud_location_info: + if location is not None: + tmp.append( + { + "id": to_native(location.id), + "name": to_native(location.name), + "description": to_native(location.description), + "city": to_native(location.city), + "country": to_native(location.country), + } + ) + return tmp + + def get_locations(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_location_info = [self.client.locations.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_location_info = [self.client.locations.get_by_name(self.module.params.get("name"))] + else: + self.hcloud_location_info = self.client.locations.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudLocationInfo.define_module() + hcloud = AnsibleHCloudLocationInfo(module) + + hcloud.get_locations() + result = hcloud.get_result() + + ansible_info = {"hcloud_location_info": result["hcloud_location_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/network.py b/ansible_collections/hetzner/hcloud/plugins/modules/network.py new file mode 100644 index 000000000..24e45a48d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/network.py @@ -0,0 +1,259 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: network + +short_description: Create and manage cloud Networks on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Networks on the Hetzner Cloud. + - You need at least hcloud-python 1.3.0. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud Networks to manage. + - Only required if no Network I(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Network to manage. + - Only required if no Network I(id) is given or a Network does not exist. + type: str + ip_range: + description: + - IP range of the Network. + - Required if Network does not exist. + type: str + expose_routes_to_vswitch: + description: + - Indicates if the routes from this network should be exposed to the vSwitch connection. + - The exposing only takes effect if a vSwitch connection is active. + type: bool + labels: + description: + - User-defined labels (key-value pairs). + type: dict + delete_protection: + description: + - Protect the Network for deletion. + type: bool + state: + description: + - State of the Network. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic network + hetzner.hcloud.network: + name: my-network + ip_range: 10.0.0.0/8 + state: present + +- name: Ensure the Network is absent (remove if needed) + hetzner.hcloud.network: + name: my-network + state: absent +""" + +RETURN = """ +hcloud_network: + description: The Network + returned: always + type: complex + contains: + id: + description: ID of the Network + type: int + returned: always + sample: 12345 + name: + description: Name of the Network + type: str + returned: always + sample: my-volume + ip_range: + description: IP range of the Network + type: str + returned: always + sample: 10.0.0.0/8 + expose_routes_to_vswitch: + description: Indicates if the routes from this network should be exposed to the vSwitch connection. + type: bool + returned: always + sample: false + delete_protection: + description: True if Network is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.networks import BoundNetwork + + +class AnsibleHCloudNetwork(AnsibleHCloud): + represent = "hcloud_network" + + hcloud_network: BoundNetwork | None = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_network.id), + "name": to_native(self.hcloud_network.name), + "ip_range": to_native(self.hcloud_network.ip_range), + "expose_routes_to_vswitch": self.hcloud_network.expose_routes_to_vswitch, + "delete_protection": self.hcloud_network.protection["delete"], + "labels": self.hcloud_network.labels, + } + + def _get_network(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_network = self.client.networks.get_by_id(self.module.params.get("id")) + else: + self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_network(self): + self.module.fail_on_missing_params(required_params=["name", "ip_range"]) + params = { + "name": self.module.params.get("name"), + "ip_range": self.module.params.get("ip_range"), + "labels": self.module.params.get("labels"), + } + + expose_routes_to_vswitch = self.module.params.get("expose_routes_to_vswitch") + if expose_routes_to_vswitch is not None: + params["expose_routes_to_vswitch"] = expose_routes_to_vswitch + + try: + if not self.module.check_mode: + self.client.networks.create(**params) + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_network() + self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_network() + + def _update_network(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_network.labels: + if not self.module.check_mode: + self.hcloud_network.update(labels=labels) + self._mark_as_changed() + + ip_range = self.module.params.get("ip_range") + if ip_range is not None and ip_range != self.hcloud_network.ip_range: + if not self.module.check_mode: + self.hcloud_network.change_ip_range(ip_range=ip_range).wait_until_finished() + self._mark_as_changed() + + expose_routes_to_vswitch = self.module.params.get("expose_routes_to_vswitch") + if ( + expose_routes_to_vswitch is not None + and expose_routes_to_vswitch != self.hcloud_network.expose_routes_to_vswitch + ): + if not self.module.check_mode: + self.hcloud_network.update(expose_routes_to_vswitch=expose_routes_to_vswitch) + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_network.protection["delete"]: + if not self.module.check_mode: + self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._get_network() + + def present_network(self): + self._get_network() + if self.hcloud_network is None: + self._create_network() + else: + self._update_network() + + def delete_network(self): + try: + self._get_network() + if self.hcloud_network is not None: + if not self.module.check_mode: + self.client.networks.delete(self.hcloud_network) + self._mark_as_changed() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self.hcloud_network = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + ip_range={"type": "str"}, + expose_routes_to_vswitch={"type": "bool"}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudNetwork.define_module() + + hcloud = AnsibleHCloudNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_network() + elif state == "present": + hcloud.present_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/network_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/network_info.py new file mode 100644 index 000000000..4008352b4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/network_info.py @@ -0,0 +1,296 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: network_info + +short_description: Gather info about your Hetzner Cloud networks. + + +description: + - Gather info about your Hetzner Cloud networks. + +author: + - Christopher Schmitt (@cschmitt-hcloud) + +options: + id: + description: + - The ID of the network you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the network you want to get. + type: str + label_selector: + description: + - The label selector for the network you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud network info + local_action: + module: hcloud_network_info + +- name: Print the gathered info + debug: + var: hcloud_network_info +""" + +RETURN = """ +hcloud_network_info: + description: The network info as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the network + returned: always + type: int + sample: 1937415 + name: + description: Name of the network + returned: always + type: str + sample: awesome-network + ip_range: + description: IP range of the network + returned: always + type: str + sample: 10.0.0.0/16 + subnetworks: + description: Subnetworks belonging to the network + returned: always + type: complex + contains: + type: + description: Type of the subnetwork. + returned: always + type: str + sample: cloud + network_zone: + description: Network of the subnetwork. + returned: always + type: str + sample: eu-central + ip_range: + description: IP range of the subnetwork + returned: always + type: str + sample: 10.0.0.0/24 + gateway: + description: Gateway of this subnetwork + returned: always + type: str + sample: 10.0.0.1 + routes: + description: Routes belonging to the network + returned: always + type: complex + contains: + ip_range: + description: Destination network or host of this route. + returned: always + type: str + sample: 10.0.0.0/16 + gateway: + description: Gateway of this route + returned: always + type: str + sample: 10.0.0.1 + expose_routes_to_vswitch: + description: Indicates if the routes from this network should be exposed to the vSwitch connection. + returned: always + type: bool + sample: false + servers: + description: Servers attached to the network + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server, None if not existing + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server, None if not existing + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if the network is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: Labels of the network + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.networks import BoundNetwork + + +class AnsibleHCloudNetworkInfo(AnsibleHCloud): + represent = "hcloud_network_info" + + hcloud_network_info: list[BoundNetwork] | None = None + + def _prepare_result(self): + tmp = [] + + for network in self.hcloud_network_info: + if network is not None: + subnets = [] + for subnet in network.subnets: + prepared_subnet = { + "type": subnet.type, + "ip_range": subnet.ip_range, + "network_zone": subnet.network_zone, + "gateway": subnet.gateway, + } + subnets.append(prepared_subnet) + routes = [] + for route in network.routes: + prepared_route = {"destination": route.destination, "gateway": route.gateway} + routes.append(prepared_route) + + servers = [] + for server in network.servers: + image = None if server.image is None else to_native(server.image.name) + ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) + ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) + prepared_server = { + "id": to_native(server.id), + "name": to_native(server.name), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "image": image, + "server_type": to_native(server.server_type.name), + "datacenter": to_native(server.datacenter.name), + "location": to_native(server.datacenter.location.name), + "rescue_enabled": server.rescue_enabled, + "backup_window": to_native(server.backup_window), + "labels": server.labels, + "status": to_native(server.status), + } + servers.append(prepared_server) + + tmp.append( + { + "id": to_native(network.id), + "name": to_native(network.name), + "ip_range": to_native(network.ip_range), + "subnetworks": subnets, + "routes": routes, + "expose_routes_to_vswitch": network.expose_routes_to_vswitch, + "servers": servers, + "labels": network.labels, + "delete_protection": network.protection["delete"], + } + ) + return tmp + + def get_networks(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_network_info = [self.client.networks.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_network_info = [self.client.networks.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_network_info = self.client.networks.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_network_info = self.client.networks.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudNetworkInfo.define_module() + hcloud = AnsibleHCloudNetworkInfo(module) + + hcloud.get_networks() + result = hcloud.get_result() + + info = {"hcloud_network_info": result["hcloud_network_info"]} + module.exit_json(**info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/placement_group.py b/ansible_collections/hetzner/hcloud/plugins/modules/placement_group.py new file mode 100644 index 000000000..ba26fad22 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/placement_group.py @@ -0,0 +1,220 @@ +#!/usr/bin/python + +# 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 annotations + +DOCUMENTATION = """ +--- +module: placement_group + +short_description: Create and manage placement groups on the Hetzner Cloud. + + +description: + - Create, update and manage placement groups on the Hetzner Cloud. + +author: + - Adrian Huber (@Adi146) + +options: + id: + description: + - The ID of the Hetzner Cloud placement group to manage. + - Only required if no placement group I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud placement group to manage. + - Only required if no placement group I(id) is given, or a placement group does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + type: + description: + - The Type of the Hetzner Cloud placement group. + type: str + state: + description: + - State of the placement group. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic placement group + hetzner.hcloud.placement_group: + name: my-placement-group + state: present + type: spread + +- name: Create a placement group with labels + hetzner.hcloud.placement_group: + name: my-placement-group + type: spread + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the placement group is absent (remove if needed) + hetzner.hcloud.placement_group: + name: my-placement-group + state: absent +""" + +RETURN = """ +hcloud_placement_group: + description: The placement group instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the placement group + returned: always + type: int + sample: 1937415 + name: + description: Name of the placement group + returned: always + type: str + sample: my placement group + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + type: + description: Type of the placement group + returned: always + type: str + sample: spread + servers: + description: Server IDs of the placement group + returned: always + type: list + elements: int + sample: + - 4711 + - 4712 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.placement_groups import BoundPlacementGroup + + +class AnsibleHCloudPlacementGroup(AnsibleHCloud): + represent = "hcloud_placement_group" + + hcloud_placement_group: BoundPlacementGroup | None = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_placement_group.id), + "name": to_native(self.hcloud_placement_group.name), + "labels": self.hcloud_placement_group.labels, + "type": to_native(self.hcloud_placement_group.type), + "servers": self.hcloud_placement_group.servers, + } + + def _get_placement_group(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_placement_group = self.client.placement_groups.get_by_id(self.module.params.get("id")) + elif self.module.params.get("name") is not None: + self.hcloud_placement_group = self.client.placement_groups.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_placement_group(self): + self.module.fail_on_missing_params(required_params=["name"]) + params = { + "name": self.module.params.get("name"), + "type": self.module.params.get("type"), + "labels": self.module.params.get("labels"), + } + if not self.module.check_mode: + try: + self.client.placement_groups.create(**params) + except HCloudException as exception: + self.fail_json_hcloud(exception, params=params) + self._mark_as_changed() + self._get_placement_group() + + def _update_placement_group(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_placement_group.name != name: + self.module.fail_on_missing_params(required_params=["id"]) + if not self.module.check_mode: + self.hcloud_placement_group.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_placement_group.labels != labels: + if not self.module.check_mode: + self.hcloud_placement_group.update(labels=labels) + self._mark_as_changed() + + self._get_placement_group() + + def present_placement_group(self): + self._get_placement_group() + if self.hcloud_placement_group is None: + self._create_placement_group() + else: + self._update_placement_group() + + def delete_placement_group(self): + self._get_placement_group() + if self.hcloud_placement_group is not None: + if not self.module.check_mode: + self.client.placement_groups.delete(self.hcloud_placement_group) + self._mark_as_changed() + self.hcloud_placement_group = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + labels={"type": "dict"}, + type={"type": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + required_if=[["state", "present", ["name"]]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudPlacementGroup.define_module() + + hcloud = AnsibleHCloudPlacementGroup(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_placement_group() + elif state == "present": + hcloud.present_placement_group() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/primary_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/primary_ip.py new file mode 100644 index 000000000..607f6c7e1 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/primary_ip.py @@ -0,0 +1,260 @@ +#!/usr/bin/python + +# Copyright: (c) 2022, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: primary_ip + +short_description: Create and manage cloud Primary IPs on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Primary IPs on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 1.8.0 +options: + id: + description: + - The ID of the Hetzner Cloud Primary IPs to manage. + - Only required if no Primary IP I(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Primary IPs to manage. + - Only required if no Primary IP I(id) is given or a Primary IP does not exist. + type: str + datacenter: + description: + - Home Location of the Hetzner Cloud Primary IP. + - Required if no I(server) is given and Primary IP does not exist. + type: str + type: + description: + - Type of the Primary IP. + - Required if Primary IP does not exist + choices: [ ipv4, ipv6 ] + type: str + auto_delete: + description: + - Delete this Primary IP when the resource it is assigned to is deleted + type: bool + default: no + delete_protection: + description: + - Protect the Primary IP for deletion. + type: bool + labels: + description: + - User-defined labels (key-value pairs). + type: dict + state: + description: + - State of the Primary IP. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic IPv4 Primary IP + hetzner.hcloud.primary_ip: + name: my-primary-ip + datacenter: fsn1-dc14 + type: ipv4 + state: present +- name: Create a basic IPv6 Primary IP + hetzner.hcloud.primary_ip: + name: my-primary-ip + datacenter: fsn1-dc14 + type: ipv6 + state: present +- name: Primary IP should be absent + hetzner.hcloud.primary_ip: + name: my-primary-ip + state: absent +""" + +RETURN = """ +hcloud_primary_ip: + description: The Primary IP instance + returned: Always + type: complex + contains: + id: + description: ID of the Primary IP + type: int + returned: Always + sample: 12345 + name: + description: Name of the Primary IP + type: str + returned: Always + sample: my-primary-ip + ip: + description: IP Address of the Primary IP + type: str + returned: Always + sample: 116.203.104.109 + type: + description: Type of the Primary IP + type: str + returned: Always + sample: ipv4 + datacenter: + description: Name of the datacenter of the Primary IP + type: str + returned: Always + sample: fsn1-dc14 + delete_protection: + description: True if Primary IP is protected for deletion + type: bool + returned: always + sample: false + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.primary_ips import BoundPrimaryIP + + +class AnsibleHCloudPrimaryIP(AnsibleHCloud): + represent = "hcloud_primary_ip" + + hcloud_primary_ip: BoundPrimaryIP | None = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_primary_ip.id), + "name": to_native(self.hcloud_primary_ip.name), + "ip": to_native(self.hcloud_primary_ip.ip), + "type": to_native(self.hcloud_primary_ip.type), + "datacenter": to_native(self.hcloud_primary_ip.datacenter.name), + "labels": self.hcloud_primary_ip.labels, + "delete_protection": self.hcloud_primary_ip.protection["delete"], + } + + def _get_primary_ip(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_primary_ip = self.client.primary_ips.get_by_id(self.module.params.get("id")) + else: + self.hcloud_primary_ip = self.client.primary_ips.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_primary_ip(self): + self.module.fail_on_missing_params(required_params=["type", "datacenter"]) + try: + params = { + "type": self.module.params.get("type"), + "name": self.module.params.get("name"), + "datacenter": self.client.datacenters.get_by_name(self.module.params.get("datacenter")), + } + + if self.module.params.get("labels") is not None: + params["labels"] = self.module.params.get("labels") + if not self.module.check_mode: + resp = self.client.primary_ips.create(**params) + self.hcloud_primary_ip = resp.primary_ip + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_primary_ip() + + def _update_primary_ip(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_primary_ip.labels: + if not self.module.check_mode: + self.hcloud_primary_ip.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_primary_ip.protection["delete"]: + if not self.module.check_mode: + self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_primary_ip() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def present_primary_ip(self): + self._get_primary_ip() + if self.hcloud_primary_ip is None: + self._create_primary_ip() + else: + self._update_primary_ip() + + def delete_primary_ip(self): + try: + self._get_primary_ip() + if self.hcloud_primary_ip is not None: + if not self.module.check_mode: + self.client.primary_ips.delete(self.hcloud_primary_ip) + self._mark_as_changed() + self.hcloud_primary_ip = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + datacenter={"type": "str"}, + auto_delete={"type": "bool", "default": False}, + type={"choices": ["ipv4", "ipv6"]}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudPrimaryIP.define_module() + + hcloud = AnsibleHCloudPrimaryIP(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_primary_ip() + elif state == "present": + hcloud.present_primary_ip() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/primary_ip_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/primary_ip_info.py new file mode 100644 index 000000000..c0bfdbb35 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/primary_ip_info.py @@ -0,0 +1,203 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: primary_ip_info + +short_description: Gather infos about the Hetzner Cloud Primary IPs. + +description: + - Gather facts about your Hetzner Cloud Primary IPs. + +author: + - Lukas Kaemmerling (@LKaemmerling) + - Kevin Castner (@kcastner) + +options: + id: + description: + - The ID of the Primary IP you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name for the Primary IP you want to get. + type: str + label_selector: + description: + - The label selector for the Primary IP you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud Primary IP infos + hetzner.hcloud.primary_ip_info: + register: output + +- name: Gather hcloud Primary IP infos by id + hetzner.hcloud.primary_ip_info: + id: 673954 + register: output + +- name: Gather hcloud Primary IP infos by name + hetzner.hcloud.primary_ip_info: + name: srv1-v4 + register: output + +- name: Gather hcloud Primary IP infos by label + hetzner.hcloud.primary_ip_info: + label_selector: srv03-ips + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_primary_ip_info: + description: The Primary IP infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Primary IP + returned: always + type: int + sample: 1937415 + name: + description: Name of the Primary IP + returned: always + type: str + sample: my-primary-ip + ip: + description: IP address of the Primary IP + returned: always + type: str + sample: 131.232.99.1 + type: + description: Type of the Primary IP + returned: always + type: str + sample: ipv4 + assignee_id: + description: Numeric identifier of the ressource where the Primary IP is assigned to. + returned: always + type: int + sample: 19584637 + assignee_type: + description: Name of the type where the Primary IP is assigned to. + returned: always + type: str + sample: server + home_location: + description: Location with datacenter where the Primary IP was created in + returned: always + type: str + sample: fsn1-dc1 + dns_ptr: + description: Shows the DNS PTR Record for Primary IP. + returned: always + type: str + sample: srv01.example.com + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if the Primary IP is protected for deletion + returned: always + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.primary_ips import BoundPrimaryIP + + +class AnsibleHCloudPrimaryIPInfo(AnsibleHCloud): + represent = "hcloud_primary_ip_info" + + hcloud_primary_ip_info: list[BoundPrimaryIP] | None = None + + def _prepare_result(self): + tmp = [] + + for primary_ip in self.hcloud_primary_ip_info: + if primary_ip is not None: + dns_ptr = None + if len(primary_ip.dns_ptr) > 0: + dns_ptr = primary_ip.dns_ptr[0]["dns_ptr"] + tmp.append( + { + "id": to_native(primary_ip.id), + "name": to_native(primary_ip.name), + "ip": to_native(primary_ip.ip), + "type": to_native(primary_ip.type), + "assignee_id": ( + to_native(primary_ip.assignee_id) if primary_ip.assignee_id is not None else None + ), + "assignee_type": to_native(primary_ip.assignee_type), + "home_location": to_native(primary_ip.datacenter.name), + "dns_ptr": to_native(dns_ptr) if dns_ptr is not None else None, + "labels": primary_ip.labels, + "delete_protection": primary_ip.protection["delete"], + } + ) + + return tmp + + def get_primary_ips(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_primary_ip_info = [self.client.primary_ips.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_primary_ip_info = [self.client.primary_ips.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_primary_ip_info = self.client.primary_ips.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_primary_ip_info = self.client.primary_ips.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + label_selector={"type": "str"}, + name={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudPrimaryIPInfo.define_module() + hcloud = AnsibleHCloudPrimaryIPInfo(module) + + hcloud.get_primary_ips() + result = hcloud.get_result() + + ansible_info = {"hcloud_primary_ip_info": result["hcloud_primary_ip_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/rdns.py b/ansible_collections/hetzner/hcloud/plugins/modules/rdns.py new file mode 100644 index 000000000..b2decdec8 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/rdns.py @@ -0,0 +1,360 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: rdns + +short_description: Create and manage reverse DNS entries on the Hetzner Cloud. + + +description: + - Create, update and delete reverse DNS entries on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + server: + description: + - Name or ID of the Hetzner Cloud server you want to add the reverse DNS entry to. + type: str + floating_ip: + description: + - Name or ID of the Hetzner Cloud Floating IP you want to add the reverse DNS entry to. + type: str + primary_ip: + description: + - Name or ID of the Hetzner Cloud Primary IP you want to add the reverse DNS entry to. + type: str + load_balancer: + description: + - Name or ID of the Hetzner Cloud Load Balancer you want to add the reverse DNS entry to. + type: str + ip_address: + description: + - The IP address that should point to I(dns_ptr). + type: str + required: true + dns_ptr: + description: + - The DNS address the I(ip_address) should resolve to. + - Omit the param to reset the reverse DNS entry to the default value. + type: str + state: + description: + - State of the reverse DNS entry. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a reverse DNS entry for a server + hetzner.hcloud.rdns: + server: my-server + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Create a reverse DNS entry for a Floating IP + hetzner.hcloud.rdns: + floating_ip: my-floating-ip + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Create a reverse DNS entry for a Primary IP + hetzner.hcloud.rdns: + primary_ip: my-primary-ip + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Create a reverse DNS entry for a Load Balancer + hetzner.hcloud.rdns: + load_balancer: my-load-balancer + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Ensure the reverse DNS entry is absent (remove if needed) + hetzner.hcloud.rdns: + server: my-server + ip_address: 123.123.123.123 + dns_ptr: example.com + state: absent +""" + +RETURN = """ +hcloud_rdns: + description: The reverse DNS entry + returned: always + type: complex + contains: + server: + description: Name of the server + type: str + returned: always + sample: my-server + floating_ip: + description: Name of the Floating IP + type: str + returned: always + sample: my-floating-ip + primary_ip: + description: Name of the Primary IP + type: str + returned: always + sample: my-primary-ip + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-load-balancer + ip_address: + description: The IP address that point to the DNS ptr + type: str + returned: always + sample: 123.123.123.123 + dns_ptr: + description: The DNS that resolves to the IP + type: str + returned: always + sample: example.com +""" + +import ipaddress +from typing import Any + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.floating_ips import BoundFloatingIP +from ..module_utils.vendor.hcloud.load_balancers import BoundLoadBalancer +from ..module_utils.vendor.hcloud.primary_ips import BoundPrimaryIP +from ..module_utils.vendor.hcloud.servers import BoundServer + + +class AnsibleHCloudReverseDNS(AnsibleHCloud): + represent = "hcloud_rdns" + + hcloud_resource: BoundServer | BoundFloatingIP | BoundLoadBalancer | BoundPrimaryIP | None = None + hcloud_rdns: dict[str, Any] | None = None + + def _prepare_result(self): + result = { + "server": None, + "floating_ip": None, + "load_balancer": None, + "ip_address": to_native(self.hcloud_rdns["ip_address"]), + "dns_ptr": to_native(self.hcloud_rdns["dns_ptr"]), + } + + if self.module.params.get("server"): + result["server"] = to_native(self.hcloud_resource.name) + elif self.module.params.get("floating_ip"): + result["floating_ip"] = to_native(self.hcloud_resource.name) + elif self.module.params.get("load_balancer"): + result["load_balancer"] = to_native(self.hcloud_resource.name) + elif self.module.params.get("primary_ip"): + result["primary_ip"] = to_native(self.hcloud_resource.name) + return result + + def _get_resource(self): + try: + if self.module.params.get("server"): + self.hcloud_resource = self._client_get_by_name_or_id( + "servers", + self.module.params.get("server"), + ) + elif self.module.params.get("floating_ip"): + self.hcloud_resource = self._client_get_by_name_or_id( + "floating_ips", + self.module.params.get("floating_ip"), + ) + elif self.module.params.get("primary_ip"): + self.hcloud_resource = self._client_get_by_name_or_id( + "primary_ips", + self.module.params.get("primary_ip"), + ) + elif self.module.params.get("load_balancer"): + self.hcloud_resource = self._client_get_by_name_or_id( + "load_balancers", + self.module.params.get("load_balancer"), + ) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _get_rdns(self): + ip_address = self.module.params.get("ip_address") + + try: + ip_address_obj = ipaddress.ip_address(ip_address) + except ValueError: + self.module.fail_json(msg=f"The given IP address is not valid: {ip_address}") + + if ip_address_obj.version == 4: + if self.module.params.get("server"): + if self.hcloud_resource.public_net.ipv4.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.public_net.ipv4.ip, + "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr, + } + else: + self.module.fail_json(msg="The selected server does not have this IP address") + elif self.module.params.get("floating_ip"): + if self.hcloud_resource.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.ip, + "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"], + } + else: + self.module.fail_json(msg="The selected Floating IP does not have this IP address") + elif self.module.params.get("primary_ip"): + if self.hcloud_resource.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.ip, + "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"], + } + else: + self.module.fail_json(msg="The selected Primary IP does not have this IP address") + elif self.module.params.get("load_balancer"): + if self.hcloud_resource.public_net.ipv4.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.public_net.ipv4.ip, + "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr, + } + else: + self.module.fail_json(msg="The selected Load Balancer does not have this IP address") + + elif ip_address_obj.version == 6: + if self.module.params.get("server"): + for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + elif self.module.params.get("floating_ip"): + for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + elif self.module.params.get("primary_ip"): + for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + elif self.module.params.get("load_balancer"): + for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + + def _create_rdns(self): + self.module.fail_on_missing_params(required_params=["dns_ptr"]) + params = { + "ip": self.module.params.get("ip_address"), + "dns_ptr": self.module.params.get("dns_ptr"), + } + + if not self.module.check_mode: + try: + self.hcloud_resource.change_dns_ptr(**params).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_resource() + self._get_rdns() + + def _update_rdns(self): + dns_ptr = self.module.params.get("dns_ptr") + if dns_ptr != self.hcloud_rdns["dns_ptr"]: + params = { + "ip": self.module.params.get("ip_address"), + "dns_ptr": dns_ptr, + } + + if not self.module.check_mode: + try: + self.hcloud_resource.change_dns_ptr(**params).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_resource() + self._get_rdns() + + def present_rdns(self): + self._get_resource() + self._get_rdns() + if self.hcloud_rdns is None: + self._create_rdns() + else: + self._update_rdns() + + def delete_rdns(self): + self._get_resource() + self._get_rdns() + if self.hcloud_rdns is not None: + if not self.module.check_mode: + try: + self.hcloud_resource.change_dns_ptr(ip=self.hcloud_rdns["ip_address"], dns_ptr=None) + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_rdns = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + server={"type": "str"}, + floating_ip={"type": "str"}, + load_balancer={"type": "str"}, + primary_ip={"type": "str"}, + ip_address={"type": "str", "required": True}, + dns_ptr={"type": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["server", "floating_ip", "load_balancer", "primary_ip"]], + mutually_exclusive=[["server", "floating_ip", "load_balancer", "primary_ip"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudReverseDNS.define_module() + + hcloud = AnsibleHCloudReverseDNS(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_rdns() + elif state == "present": + hcloud.present_rdns() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/route.py b/ansible_collections/hetzner/hcloud/plugins/modules/route.py new file mode 100644 index 000000000..3c96a7382 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/route.py @@ -0,0 +1,190 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: route + +short_description: Create and delete cloud routes on the Hetzner Cloud. + + +description: + - Create, update and delete cloud routes on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - Name or ID of the Hetzner Cloud Network. + type: str + required: true + destination: + description: + - Destination network or host of this route. + type: str + required: true + gateway: + description: + - Gateway for the route. + type: str + required: true + state: + description: + - State of the route. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic route + hetzner.hcloud.route: + network: my-network + destination: 10.100.1.0/24 + gateway: 10.0.1.1 + state: present + +- name: Ensure the route is absent + hetzner.hcloud.route: + network: my-network + destination: 10.100.1.0/24 + gateway: 10.0.1.1 + state: absent +""" + +RETURN = """ +hcloud_route: + description: One Route of a Network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + destination: + description: Destination network or host of this route + type: str + returned: always + sample: 10.0.0.0/8 + gateway: + description: Gateway of the route + type: str + returned: always + sample: 10.0.0.1 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.networks import BoundNetwork, NetworkRoute + + +class AnsibleHCloudRoute(AnsibleHCloud): + represent = "hcloud_route" + + hcloud_network: BoundNetwork | None = None + hcloud_route: NetworkRoute | None = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "destination": to_native(self.hcloud_route.destination), + "gateway": self.hcloud_route.gateway, + } + + def _get_network(self): + try: + self.hcloud_network = self._client_get_by_name_or_id( + "networks", + self.module.params.get("network"), + ) + self.hcloud_route = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _get_route(self): + destination = self.module.params.get("destination") + gateway = self.module.params.get("gateway") + for route in self.hcloud_network.routes: + if route.destination == destination and route.gateway == gateway: + self.hcloud_route = route + + def _create_route(self): + route = NetworkRoute( + destination=self.module.params.get("destination"), gateway=self.module.params.get("gateway") + ) + + if not self.module.check_mode: + try: + self.hcloud_network.add_route(route=route).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + self._mark_as_changed() + self._get_network() + self._get_route() + + def present_route(self): + self._get_network() + self._get_route() + if self.hcloud_route is None: + self._create_route() + + def delete_route(self): + self._get_network() + self._get_route() + if self.hcloud_route is not None and self.hcloud_network is not None: + if not self.module.check_mode: + try: + self.hcloud_network.delete_route(self.hcloud_route).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_route = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + gateway={"type": "str", "required": True}, + destination={"type": "str", "required": True}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudRoute.define_module() + + hcloud = AnsibleHCloudRoute(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_route() + elif state == "present": + hcloud.present_route() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/server.py b/ansible_collections/hetzner/hcloud/plugins/modules/server.py new file mode 100644 index 000000000..f5cadb807 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/server.py @@ -0,0 +1,952 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: server + +short_description: Create and manage cloud servers on the Hetzner Cloud. + + +description: + - Create, update and manage cloud servers on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud server to manage. + - Only required if no server I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud server to manage. + - Only required if no server I(id) is given or a server does not exist. + type: str + server_type: + description: + - The Server Type of the Hetzner Cloud server to manage. + - Required if server does not exist. + type: str + ssh_keys: + description: + - List of SSH key names + - The key names correspond to the SSH keys configured for your + Hetzner Cloud account access. + type: list + elements: str + volumes: + description: + - List of Volumes IDs that should be attached to the server on server creation. + type: list + elements: str + firewalls: + description: + - List of Firewall IDs that should be attached to the server on server creation. + type: list + elements: str + image: + description: + - Image the server should be created from. + - Required if server does not exist. + type: str + location: + description: + - Location of Server. + - Required if no I(datacenter) is given and server does not exist. + type: str + datacenter: + description: + - Datacenter of Server. + - Required if no I(location) is given and server does not exist. + type: str + backups: + description: + - Enable or disable Backups for the given Server. + type: bool + upgrade_disk: + description: + - Resize the disk size, when resizing a server. + - If you want to downgrade the server later, this value should be False. + type: bool + default: false + enable_ipv4: + description: + - Enables the public ipv4 address + type: bool + default: true + enable_ipv6: + description: + - Enables the public ipv6 address + type: bool + default: true + ipv4: + description: + - ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a new ipv4 Primary IP will automatically be created + type: str + ipv6: + description: + - ID of the ipv6 Primary IP to use. If omitted and enable_ipv6 is true, a new ipv6 Primary IP will automatically be created. + type: str + private_networks: + description: + - List of private networks the server is attached to (name or ID) + - If None, private networks are left as they are (e.g. if previously added by hcloud_server_network), + if it has any other value (including []), only those networks are attached to the server. + type: list + elements: str + force_upgrade: + description: + - Deprecated + - Force the upgrade of the server. + - Power off the server if it is running on upgrade. + type: bool + force: + description: + - Force the update of the server. + - May power off the server if update. + type: bool + default: false + allow_deprecated_image: + description: + - Allows the creation of servers with deprecated images. + type: bool + default: false + user_data: + description: + - User Data to be passed to the server on creation. + - Only used if server does not exist. + type: str + rescue_mode: + description: + - Add the Hetzner rescue system type you want the server to be booted into. + type: str + labels: + description: + - User-defined labels (key-value pairs). + type: dict + delete_protection: + description: + - Protect the Server for deletion. + - Needs to be the same as I(rebuild_protection). + type: bool + rebuild_protection: + description: + - Protect the Server for rebuild. + - Needs to be the same as I(delete_protection). + type: bool + placement_group: + description: + - Placement Group of the server. + type: str + state: + description: + - State of the server. + default: present + choices: [ absent, present, restarted, started, stopped, rebuild ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Create a basic server + hetzner.hcloud.server: + name: my-server + server_type: cx11 + image: ubuntu-22.04 + state: present + +- name: Create a basic server with ssh key + hetzner.hcloud.server: + name: my-server + server_type: cx11 + image: ubuntu-22.04 + location: fsn1 + ssh_keys: + - me@myorganisation + state: present + +- name: Resize an existing server + hetzner.hcloud.server: + name: my-server + server_type: cx21 + upgrade_disk: true + state: present + +- name: Ensure the server is absent (remove if needed) + hetzner.hcloud.server: + name: my-server + state: absent + +- name: Ensure the server is started + hetzner.hcloud.server: + name: my-server + state: started + +- name: Ensure the server is stopped + hetzner.hcloud.server: + name: my-server + state: stopped + +- name: Ensure the server is restarted + hetzner.hcloud.server: + name: my-server + state: restarted + +- name: Ensure the server is will be booted in rescue mode and therefore restarted + hetzner.hcloud.server: + name: my-server + rescue_mode: linux64 + state: restarted + +- name: Ensure the server is rebuild + hetzner.hcloud.server: + name: my-server + image: ubuntu-22.04 + state: rebuild + +- name: Add server to placement group + hetzner.hcloud.server: + name: my-server + placement_group: my-placement-group + force: true + state: present + +- name: Remove server from placement group + hetzner.hcloud.server: + name: my-server + placement_group: + state: present + +- name: Add server with private network only + hetzner.hcloud.server: + name: my-server + enable_ipv4: false + enable_ipv6: false + private_networks: + - my-network + - 4711 + state: present +""" + +RETURN = """ +hcloud_server: + description: The server instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + created: + description: Point in time when the Server was created (in ISO-8601 format) + returned: always + type: str + sample: "2023-11-06T13:36:56+00:00" + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + private_networks: + description: List of private networks the server is attached to (name or ID) + returned: always + type: list + elements: str + sample: ['my-network', 'another-network', '4711'] + private_networks_info: + description: List of private networks the server is attached to (dict with name and ip) + returned: always + type: list + elements: dict + sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + placement_group: + description: Placement Group of the server + type: str + returned: always + sample: 4711 + version_added: "1.5.0" + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from datetime import datetime, timedelta, timezone + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.firewalls import FirewallResource +from ..module_utils.vendor.hcloud.servers import ( + BoundServer, + Server, + ServerCreatePublicNetwork, +) +from ..module_utils.vendor.hcloud.ssh_keys import SSHKey +from ..module_utils.vendor.hcloud.volumes import Volume + + +class AnsibleHCloudServer(AnsibleHCloud): + represent = "hcloud_server" + + hcloud_server: BoundServer | None = None + + def _prepare_result(self): + image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name) + placement_group = ( + None if self.hcloud_server.placement_group is None else to_native(self.hcloud_server.placement_group.name) + ) + ipv4_address = ( + None if self.hcloud_server.public_net.ipv4 is None else to_native(self.hcloud_server.public_net.ipv4.ip) + ) + ipv6 = None if self.hcloud_server.public_net.ipv6 is None else to_native(self.hcloud_server.public_net.ipv6.ip) + backup_window = ( + None if self.hcloud_server.backup_window is None else to_native(self.hcloud_server.backup_window) + ) + return { + "id": to_native(self.hcloud_server.id), + "name": to_native(self.hcloud_server.name), + "created": to_native(self.hcloud_server.created.isoformat()), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "private_networks": [to_native(net.network.name) for net in self.hcloud_server.private_net], + "private_networks_info": [ + {"name": to_native(net.network.name), "ip": net.ip} for net in self.hcloud_server.private_net + ], + "image": image, + "server_type": to_native(self.hcloud_server.server_type.name), + "datacenter": to_native(self.hcloud_server.datacenter.name), + "location": to_native(self.hcloud_server.datacenter.location.name), + "placement_group": placement_group, + "rescue_enabled": self.hcloud_server.rescue_enabled, + "backup_window": backup_window, + "labels": self.hcloud_server.labels, + "delete_protection": self.hcloud_server.protection["delete"], + "rebuild_protection": self.hcloud_server.protection["rebuild"], + "status": to_native(self.hcloud_server.status), + } + + def _get_server(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server = self.client.servers.get_by_id(self.module.params.get("id")) + else: + self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_server(self): + self.module.fail_on_missing_params(required_params=["name", "server_type", "image"]) + + server_type = self._get_server_type() + + params = { + "name": self.module.params.get("name"), + "server_type": server_type, + "user_data": self.module.params.get("user_data"), + "labels": self.module.params.get("labels"), + "image": self._get_image(server_type), + "placement_group": self._get_placement_group(), + "public_net": ServerCreatePublicNetwork( + enable_ipv4=self.module.params.get("enable_ipv4"), + enable_ipv6=self.module.params.get("enable_ipv6"), + ), + } + + if self.module.params.get("ipv4") is not None: + primary_ip = self.client.primary_ips.get_by_name(self.module.params.get("ipv4")) + if not primary_ip: + primary_ip = self.client.primary_ips.get_by_id(self.module.params.get("ipv4")) + params["public_net"].ipv4 = primary_ip + + if self.module.params.get("ipv6") is not None: + primary_ip = self.client.primary_ips.get_by_name(self.module.params.get("ipv6")) + if not primary_ip: + primary_ip = self.client.primary_ips.get_by_id(self.module.params.get("ipv6")) + params["public_net"].ipv6 = primary_ip + + if self.module.params.get("private_networks") is not None: + _networks = [] + for network_name_or_id in self.module.params.get("private_networks"): + _networks.append( + self.client.networks.get_by_name(network_name_or_id) + or self.client.networks.get_by_id(network_name_or_id) + ) + params["networks"] = _networks + + if self.module.params.get("ssh_keys") is not None: + params["ssh_keys"] = [SSHKey(name=ssh_key_name) for ssh_key_name in self.module.params.get("ssh_keys")] + + if self.module.params.get("volumes") is not None: + params["volumes"] = [Volume(id=volume_id) for volume_id in self.module.params.get("volumes")] + if self.module.params.get("firewalls") is not None: + params["firewalls"] = [] + for firewall_param in self.module.params.get("firewalls"): + firewall = self.client.firewalls.get_by_name(firewall_param) + if firewall is not None: + # When firewall name is not available look for id instead + params["firewalls"].append(firewall) + else: + params["firewalls"].append(self.client.firewalls.get_by_id(firewall_param)) + + if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: + # When not given, the API will choose the location. + params["location"] = None + params["datacenter"] = None + elif self.module.params.get("location") is not None and self.module.params.get("datacenter") 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("datacenter") is not None: + params["datacenter"] = self.client.datacenters.get_by_name(self.module.params.get("datacenter")) + + if self.module.params.get("state") == "stopped": + params["start_after_create"] = False + if not self.module.check_mode: + try: + resp = self.client.servers.create(**params) + self.result["root_password"] = resp.root_password + resp.action.wait_until_finished(max_retries=1000) + [action.wait_until_finished() for action in resp.next_actions] + + rescue_mode = self.module.params.get("rescue_mode") + if rescue_mode: + self._get_server() + self._set_rescue_mode(rescue_mode) + + backups = self.module.params.get("backups") + if backups: + self._get_server() + self.hcloud_server.enable_backup().wait_until_finished() + + delete_protection = self.module.params.get("delete_protection") + rebuild_protection = self.module.params.get("rebuild_protection") + if delete_protection is not None and rebuild_protection is not None: + self._get_server() + self.hcloud_server.change_protection( + delete=delete_protection, + rebuild=rebuild_protection, + ).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_server() + + def _get_image(self, server_type): + image_resp = self.client.images.get_list( + name=self.module.params.get("image"), + architecture=server_type.architecture, + include_deprecated=True, + ) + images = getattr(image_resp, "images") + image = None + if images is not None and len(images) > 0: + # If image name is not available look for id instead + image = images[0] + else: + try: + image = self.client.images.get_by_id(self.module.params.get("image")) + except HCloudException as exception: + self.fail_json_hcloud(exception, msg=f"Image {self.module.params.get('image')} was not found") + if image.deprecated is not None: + available_until = image.deprecated + timedelta(days=90) + if self.module.params.get("allow_deprecated_image"): + self.module.warn( + f"You try to use a deprecated image. The image {image.name} will " + f"continue to be available until {available_until.strftime('%Y-%m-%d')}." + ) + else: + self.module.fail_json( + msg=( + f"You try to use a deprecated image. The image {image.name} will " + f"continue to be available until {available_until.strftime('%Y-%m-%d')}. " + "If you want to use this image use allow_deprecated_image=true." + ) + ) + return image + + def _get_server_type(self): + server_type = self.client.server_types.get_by_name(self.module.params.get("server_type")) + if server_type is None: + try: + server_type = self.client.server_types.get_by_id(self.module.params.get("server_type")) + except HCloudException as exception: + self.fail_json_hcloud( + exception, + msg=f"server_type {self.module.params.get('server_type')} was not found", + ) + + self._check_and_warn_deprecated_server(server_type) + + return server_type + + def _check_and_warn_deprecated_server(self, server_type): + if server_type.deprecation is None: + return + + if server_type.deprecation.unavailable_after < datetime.now(timezone.utc): + self.module.warn( + f"Attention: The server plan {server_type.name} is deprecated and can " + "no longer be ordered. Existing servers of that plan will continue to " + "work as before and no action is required on your part. " + "It is possible to migrate this server to another server plan by setting " + "the server_type parameter on the hetzner.hcloud.server module." + ) + else: + server_type_unavailable_date = server_type.deprecation.unavailable_after.strftime("%Y-%m-%d") + self.module.warn( + f"Attention: The server plan {server_type.name} is deprecated and will " + f"no longer be available for order as of {server_type_unavailable_date}. " + "Existing servers of that plan will continue to work as before and no " + "action is required on your part. " + "It is possible to migrate this server to another server plan by setting " + "the server_type parameter on the hetzner.hcloud.server module." + ) + + def _get_placement_group(self): + if self.module.params.get("placement_group") is None: + return None + + placement_group = self.client.placement_groups.get_by_name(self.module.params.get("placement_group")) + if placement_group is None: + try: + placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group")) + except HCloudException as exception: + self.fail_json_hcloud( + exception, + msg=f"placement_group {self.module.params.get('placement_group')} was not found", + ) + + return placement_group + + def _get_primary_ip(self, field): + if self.module.params.get(field) is None: + return None + + primary_ip = self.client.primary_ips.get_by_name(self.module.params.get(field)) + if primary_ip is None: + try: + primary_ip = self.client.primary_ips.get_by_id(self.module.params.get(field)) + except HCloudException as exception: + self.fail_json_hcloud(exception, msg=f"primary_ip {self.module.params.get(field)} was not found") + + return primary_ip + + def _update_server(self): + if "force_upgrade" in self.module.params and self.module.params.get("force_upgrade") is not None: + self.module.warn("force_upgrade is deprecated, use force instead") + + try: + previous_server_status = self.hcloud_server.status + + rescue_mode = self.module.params.get("rescue_mode") + if rescue_mode and self.hcloud_server.rescue_enabled is False: + if not self.module.check_mode: + self._set_rescue_mode(rescue_mode) + self._mark_as_changed() + elif not rescue_mode and self.hcloud_server.rescue_enabled is True: + if not self.module.check_mode: + self.hcloud_server.disable_rescue().wait_until_finished() + self._mark_as_changed() + + backups = self.module.params.get("backups") + if backups and self.hcloud_server.backup_window is None: + if not self.module.check_mode: + self.hcloud_server.enable_backup().wait_until_finished() + self._mark_as_changed() + elif backups is not None and not backups and self.hcloud_server.backup_window is not None: + if not self.module.check_mode: + self.hcloud_server.disable_backup().wait_until_finished() + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_server.labels: + if not self.module.check_mode: + self.hcloud_server.update(labels=labels) + self._mark_as_changed() + + wanted_firewalls = self.module.params.get("firewalls") + if wanted_firewalls is not None: + # Removing existing but not wanted firewalls + for current_firewall in self.hcloud_server.public_net.firewalls: + if current_firewall.firewall.name not in wanted_firewalls: + self._mark_as_changed() + if self.module.check_mode: + continue + + firewall_resource = FirewallResource(type="server", server=self.hcloud_server) + actions = self.client.firewalls.remove_from_resources( + current_firewall.firewall, + [firewall_resource], + ) + for action in actions: + action.wait_until_finished() + + # Adding wanted firewalls that doesn't exist yet + for firewall_name in wanted_firewalls: + found = False + for firewall in self.hcloud_server.public_net.firewalls: + if firewall.firewall.name == firewall_name: + found = True + break + + if not found: + self._mark_as_changed() + if not self.module.check_mode: + firewall = self.client.firewalls.get_by_name(firewall_name) + if firewall is None: + self.module.fail_json(msg=f"firewall {firewall_name} was not found") + firewall_resource = FirewallResource(type="server", server=self.hcloud_server) + actions = self.client.firewalls.apply_to_resources(firewall, [firewall_resource]) + for action in actions: + action.wait_until_finished() + + if "placement_group" in self.module.params: + if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None: + if not self.module.check_mode: + self.hcloud_server.remove_from_placement_group().wait_until_finished() + self._mark_as_changed() + else: + placement_group = self._get_placement_group() + if placement_group is not None and ( + self.hcloud_server.placement_group is None + or self.hcloud_server.placement_group.id != placement_group.id + ): + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.add_to_placement_group(placement_group).wait_until_finished() + self._mark_as_changed() + + if "ipv4" in self.module.params: + if ( + self.module.params["ipv4"] is None + and self.hcloud_server.public_net.primary_ipv4 is not None + and not self.module.params.get("enable_ipv4") + ): + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished() + self._mark_as_changed() + else: + primary_ip = self._get_primary_ip("ipv4") + if primary_ip is not None and ( + self.hcloud_server.public_net.primary_ipv4 is None + or self.hcloud_server.public_net.primary_ipv4.id != primary_ip.id + ): + self.stop_server_if_forced() + if not self.module.check_mode: + if self.hcloud_server.public_net.primary_ipv4: + self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished() + primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished() + self._mark_as_changed() + if "ipv6" in self.module.params: + if ( + (self.module.params["ipv6"] is None or self.module.params["ipv6"] == "") + and self.hcloud_server.public_net.primary_ipv6 is not None + and not self.module.params.get("enable_ipv6") + ): + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished() + self._mark_as_changed() + else: + primary_ip = self._get_primary_ip("ipv6") + if primary_ip is not None and ( + self.hcloud_server.public_net.primary_ipv6 is None + or self.hcloud_server.public_net.primary_ipv6.id != primary_ip.id + ): + self.stop_server_if_forced() + if not self.module.check_mode: + if self.hcloud_server.public_net.primary_ipv6 is not None: + self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished() + primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished() + self._mark_as_changed() + if "private_networks" in self.module.params and self.module.params["private_networks"] is not None: + if not bool(self.module.params["private_networks"]): + # This handles None, "" and [] + networks_target = {} + else: + _networks = {} + for network_name_or_id in self.module.params.get("private_networks"): + _found_network = self.client.networks.get_by_name( + network_name_or_id + ) or self.client.networks.get_by_id(network_name_or_id) + _networks.update({_found_network.id: _found_network}) + networks_target = _networks + networks_is = dict() + for p_network in self.hcloud_server.private_net: + networks_is.update({p_network.network.id: p_network.network}) + for network_id in set(list(networks_is) + list(networks_target)): + if network_id in networks_is and network_id not in networks_target: + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.detach_from_network(networks_is[network_id]).wait_until_finished() + self._mark_as_changed() + elif network_id in networks_target and network_id not in networks_is: + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.attach_to_network(networks_target[network_id]).wait_until_finished() + self._mark_as_changed() + + server_type = self.module.params.get("server_type") + if server_type is not None: + if self.hcloud_server.server_type.name == server_type: + # Check if we should warn for using an deprecated server type + self._check_and_warn_deprecated_server(self.hcloud_server.server_type) + + else: + # Server type should be changed + self.stop_server_if_forced() + + timeout = 100 + if self.module.params.get("upgrade_disk"): + timeout = 1000 # When we upgrade the disk to the resize progress takes some more time. + if not self.module.check_mode: + self.hcloud_server.change_type( + server_type=self._get_server_type(), + upgrade_disk=self.module.params.get("upgrade_disk"), + ).wait_until_finished(timeout) + self._mark_as_changed() + + if not self.module.check_mode and ( + (self.module.params.get("state") == "present" and previous_server_status == Server.STATUS_RUNNING) + or self.module.params.get("state") == "started" + ): + self.start_server() + + delete_protection = self.module.params.get("delete_protection") + rebuild_protection = self.module.params.get("rebuild_protection") + if (delete_protection is not None and rebuild_protection is not None) and ( + delete_protection != self.hcloud_server.protection["delete"] + or rebuild_protection != self.hcloud_server.protection["rebuild"] + ): + if not self.module.check_mode: + self.hcloud_server.change_protection( + delete=delete_protection, + rebuild=rebuild_protection, + ).wait_until_finished() + self._mark_as_changed() + self._get_server() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _set_rescue_mode(self, rescue_mode): + if self.module.params.get("ssh_keys"): + resp = self.hcloud_server.enable_rescue( + type=rescue_mode, + ssh_keys=[ + self.client.ssh_keys.get_by_name(ssh_key_name).id + for ssh_key_name in self.module.params.get("ssh_keys") + ], + ) + else: + resp = self.hcloud_server.enable_rescue(type=rescue_mode) + resp.action.wait_until_finished() + self.result["root_password"] = resp.root_password + + def start_server(self): + try: + if self.hcloud_server: + if self.hcloud_server.status != Server.STATUS_RUNNING: + if not self.module.check_mode: + self.client.servers.power_on(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self._get_server() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def stop_server(self): + try: + if self.hcloud_server: + if self.hcloud_server.status != Server.STATUS_OFF: + if not self.module.check_mode: + self.client.servers.power_off(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self._get_server() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def stop_server_if_forced(self): + previous_server_status = self.hcloud_server.status + if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode: + if ( + self.module.params.get("force_upgrade") + or self.module.params.get("force") + or self.module.params.get("state") == "stopped" + ): + self.stop_server() # Only stopped server can be upgraded + return previous_server_status + else: + self.module.warn( + f"You can not upgrade a running instance {self.hcloud_server.name}. " + "You need to stop the instance or use force=true." + ) + + return None + + def rebuild_server(self): + self.module.fail_on_missing_params(required_params=["image"]) + try: + if not self.module.check_mode: + image = self._get_image(self.hcloud_server.server_type) + # When we rebuild the server progress takes some more time. + resp = self.client.servers.rebuild(self.hcloud_server, image, return_response=True) + resp.action.wait_until_finished(1000) + self._mark_as_changed() + + self._get_server() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def present_server(self): + self._get_server() + if self.hcloud_server is None: + self._create_server() + else: + self._update_server() + + def delete_server(self): + try: + self._get_server() + if self.hcloud_server is not None: + if not self.module.check_mode: + self.client.servers.delete(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self.hcloud_server = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + image={"type": "str"}, + server_type={"type": "str"}, + location={"type": "str"}, + datacenter={"type": "str"}, + user_data={"type": "str"}, + ssh_keys={"type": "list", "elements": "str", "no_log": False}, + volumes={"type": "list", "elements": "str"}, + firewalls={"type": "list", "elements": "str"}, + labels={"type": "dict"}, + backups={"type": "bool"}, + upgrade_disk={"type": "bool", "default": False}, + enable_ipv4={"type": "bool", "default": True}, + enable_ipv6={"type": "bool", "default": True}, + ipv4={"type": "str"}, + ipv6={"type": "str"}, + private_networks={"type": "list", "elements": "str", "default": None}, + force={"type": "bool", "default": False}, + force_upgrade={"type": "bool"}, + allow_deprecated_image={"type": "bool", "default": False}, + rescue_mode={"type": "str"}, + delete_protection={"type": "bool"}, + rebuild_protection={"type": "bool"}, + placement_group={"type": "str"}, + state={ + "choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + mutually_exclusive=[["location", "datacenter"]], + required_together=[["delete_protection", "rebuild_protection"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudServer.define_module() + + hcloud = AnsibleHCloudServer(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_server() + elif state == "present": + hcloud.present_server() + elif state == "started": + hcloud.present_server() + hcloud.start_server() + elif state == "stopped": + hcloud.present_server() + hcloud.stop_server() + elif state == "restarted": + hcloud.present_server() + hcloud.stop_server() + hcloud.start_server() + elif state == "rebuild": + hcloud.present_server() + hcloud.rebuild_server() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/server_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/server_info.py new file mode 100644 index 000000000..cee1634cb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/server_info.py @@ -0,0 +1,238 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: server_info + +short_description: Gather infos about your Hetzner Cloud servers. + + +description: + - Gather infos about your Hetzner Cloud servers. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the server you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the server you want to get. + type: str + label_selector: + description: + - The label selector for the server you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud server infos + hetzner.hcloud.server_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_server_info +""" + +RETURN = """ +hcloud_server_info: + description: The server infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + created: + description: Point in time when the Server was created (in ISO-8601 format) + returned: always + type: str + sample: "2023-11-06T13:36:56+00:00" + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + private_networks: + description: List of private networks the server is attached to (name) + returned: always + type: list + elements: str + sample: ['my-network', 'another-network'] + private_networks_info: + description: List of private networks the server is attached to (dict with name and ip) + returned: always + type: list + elements: dict + sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + placement_group: + description: Placement Group of the server + type: str + returned: always + sample: 4711 + version_added: "1.5.0" + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.servers import BoundServer + + +class AnsibleHCloudServerInfo(AnsibleHCloud): + represent = "hcloud_server_info" + + hcloud_server_info: list[BoundServer] | None = None + + def _prepare_result(self): + tmp = [] + + for server in self.hcloud_server_info: + if server is not None: + image = None if server.image is None else to_native(server.image.name) + placement_group = None if server.placement_group is None else to_native(server.placement_group.name) + ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) + ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) + backup_window = None if server.backup_window is None else to_native(server.backup_window) + tmp.append( + { + "id": to_native(server.id), + "name": to_native(server.name), + "created": to_native(server.created.isoformat()), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "private_networks": [to_native(net.network.name) for net in server.private_net], + "private_networks_info": [ + {"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net + ], + "image": image, + "server_type": to_native(server.server_type.name), + "datacenter": to_native(server.datacenter.name), + "location": to_native(server.datacenter.location.name), + "placement_group": placement_group, + "rescue_enabled": server.rescue_enabled, + "backup_window": backup_window, + "labels": server.labels, + "status": to_native(server.status), + "delete_protection": server.protection["delete"], + "rebuild_protection": server.protection["rebuild"], + } + ) + return tmp + + def get_servers(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server_info = [self.client.servers.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_server_info = [self.client.servers.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_server_info = self.client.servers.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_server_info = self.client.servers.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudServerInfo.define_module() + hcloud = AnsibleHCloudServerInfo(module) + + hcloud.get_servers() + result = hcloud.get_result() + + ansible_info = {"hcloud_server_info": result["hcloud_server_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/server_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/server_network.py new file mode 100644 index 000000000..ca80a8a76 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/server_network.py @@ -0,0 +1,246 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: server_network + +short_description: Manage the relationship between Hetzner Cloud Networks and servers + + +description: + - Create and delete the relationship Hetzner Cloud Networks and servers + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - Name or ID of the Hetzner Cloud Networks. + type: str + required: true + server: + description: + - Name or ID of the Hetzner Cloud server. + type: str + required: true + ip: + description: + - The IP the server should have. + type: str + alias_ips: + description: + - Alias IPs the server has. + type: list + elements: str + state: + description: + - State of the server_network. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic server network + hetzner.hcloud.server_network: + network: my-network + server: my-server + state: present + +- name: Create a server network and specify the ip address + hetzner.hcloud.server_network: + network: my-network + server: my-server + ip: 10.0.0.1 + state: present + +- name: Create a server network and add alias ips + hetzner.hcloud.server_network: + network: my-network + server: my-server + ip: 10.0.0.1 + alias_ips: + - 10.1.0.1 + - 10.2.0.1 + state: present + +- name: Ensure the server network is absent (remove if needed) + hetzner.hcloud.server_network: + network: my-network + server: my-server + state: absent +""" + +RETURN = """ +hcloud_server_network: + description: The relationship between a server and a network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + server: + description: Name of the server + type: str + returned: always + sample: my-server + ip: + description: IP of the server within the Network ip range + type: str + returned: always + sample: 10.0.0.8 + alias_ips: + description: Alias IPs of the server within the Network ip range + type: list + elements: str + returned: always + sample: [10.1.0.1, ...] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import APIException, HCloudException +from ..module_utils.vendor.hcloud.networks import BoundNetwork +from ..module_utils.vendor.hcloud.servers import BoundServer, PrivateNet + + +class AnsibleHCloudServerNetwork(AnsibleHCloud): + represent = "hcloud_server_network" + + hcloud_network: BoundNetwork | None = None + hcloud_server: BoundServer | None = None + hcloud_server_network: PrivateNet | None = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "server": to_native(self.hcloud_server.name), + "ip": to_native(self.hcloud_server_network.ip), + "alias_ips": self.hcloud_server_network.alias_ips, + } + + def _get_server_and_network(self): + try: + self.hcloud_network = self._client_get_by_name_or_id( + "networks", + self.module.params.get("network"), + ) + self.hcloud_server = self._client_get_by_name_or_id( + "servers", + self.module.params.get("server"), + ) + self.hcloud_server_network = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _get_server_network(self): + for private_net in self.hcloud_server.private_net: + if private_net.network.id == self.hcloud_network.id: + self.hcloud_server_network = private_net + + def _create_server_network(self): + params = { + "network": self.hcloud_network, + } + + if self.module.params.get("ip") is not None: + params["ip"] = self.module.params.get("ip") + if self.module.params.get("alias_ips") is not None: + params["alias_ips"] = self.module.params.get("alias_ips") + + if not self.module.check_mode: + try: + self.hcloud_server.attach_to_network(**params).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + self._mark_as_changed() + self._get_server_and_network() + self._get_server_network() + + def _update_server_network(self): + params = { + "network": self.hcloud_network, + } + alias_ips = self.module.params.get("alias_ips") + if alias_ips is not None and sorted(self.hcloud_server_network.alias_ips) != sorted(alias_ips): + params["alias_ips"] = alias_ips + + if not self.module.check_mode: + try: + self.hcloud_server.change_alias_ips(**params).wait_until_finished() + except APIException as exception: + self.fail_json_hcloud(exception) + + self._mark_as_changed() + self._get_server_and_network() + self._get_server_network() + + def present_server_network(self): + self._get_server_and_network() + self._get_server_network() + if self.hcloud_server_network is None: + self._create_server_network() + else: + self._update_server_network() + + def delete_server_network(self): + self._get_server_and_network() + self._get_server_network() + if self.hcloud_server_network is not None and self.hcloud_server is not None: + if not self.module.check_mode: + try: + self.hcloud_server.detach_from_network(self.hcloud_server_network.network).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_server_network = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + server={"type": "str", "required": True}, + ip={"type": "str"}, + alias_ips={"type": "list", "elements": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudServerNetwork.define_module() + + hcloud = AnsibleHCloudServerNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_server_network() + elif state == "present": + hcloud.present_server_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/server_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/server_type_info.py new file mode 100644 index 000000000..61f1f5011 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/server_type_info.py @@ -0,0 +1,204 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: server_type_info + +short_description: Gather infos about the Hetzner Cloud server types. + + +description: + - Gather infos about your Hetzner Cloud server types. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the server type you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the server type you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud server type infos + hetzner.hcloud.server_type_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_server_type_info +""" + +RETURN = """ +hcloud_server_type_info: + description: The server type infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server type + returned: always + type: int + sample: 1937415 + name: + description: Name of the server type + returned: always + type: str + sample: fsn1 + description: + description: Detail description of the server type + returned: always + type: str + sample: Falkenstein DC Park 1 + cores: + description: Number of cpu cores a server of this type will have + returned: always + type: int + sample: 1 + memory: + description: Memory a server of this type will have in GB + returned: always + type: int + sample: 1 + disk: + description: Disk size a server of this type will have in GB + returned: always + type: int + sample: 25 + storage_type: + description: Type of server boot drive + returned: always + type: str + sample: local + cpu_type: + description: Type of cpu + returned: always + type: str + sample: shared + architecture: + description: Architecture of cpu + returned: always + type: str + sample: x86 + included_traffic: + description: Free traffic per month in bytes + returned: always + type: int + sample: 21990232555520 + deprecation: + description: | + Describes if, when & how the resources was deprecated. + If this field is set to None the resource is not deprecated. If it has a value, it is considered deprecated. + returned: success + type: dict + contains: + announced: + description: Date of when the deprecation was announced. + returned: success + type: str + sample: "2021-11-09T09:00:00+00:00" + unavailable_after: + description: | + After the time in this field, the resource will not be available from the general listing + endpoint of the resource type, and it can not be used in new resources. For example, if this is + an image, you can not create new servers with this image after the mentioned date. + returned: success + type: str + sample: "2021-12-01T00:00:00+00:00" + +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.server_types import BoundServerType + + +class AnsibleHCloudServerTypeInfo(AnsibleHCloud): + represent = "hcloud_server_type_info" + + hcloud_server_type_info: list[BoundServerType] | None = None + + def _prepare_result(self): + tmp = [] + + for server_type in self.hcloud_server_type_info: + if server_type is not None: + tmp.append( + { + "id": to_native(server_type.id), + "name": to_native(server_type.name), + "description": to_native(server_type.description), + "cores": server_type.cores, + "memory": server_type.memory, + "disk": server_type.disk, + "storage_type": to_native(server_type.storage_type), + "cpu_type": to_native(server_type.cpu_type), + "architecture": to_native(server_type.architecture), + "included_traffic": server_type.included_traffic, + "deprecation": ( + { + "announced": server_type.deprecation.announced.isoformat(), + "unavailable_after": server_type.deprecation.unavailable_after.isoformat(), + } + if server_type.deprecation is not None + else None + ), + } + ) + return tmp + + def get_server_types(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server_type_info = [self.client.server_types.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_server_type_info = [self.client.server_types.get_by_name(self.module.params.get("name"))] + else: + self.hcloud_server_type_info = self.client.server_types.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudServerTypeInfo.define_module() + hcloud = AnsibleHCloudServerTypeInfo(module) + + hcloud.get_server_types() + result = hcloud.get_result() + + ansible_info = {"hcloud_server_type_info": result["hcloud_server_type_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/ssh_key.py b/ansible_collections/hetzner/hcloud/plugins/modules/ssh_key.py new file mode 100644 index 000000000..349c52c68 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/ssh_key.py @@ -0,0 +1,234 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: ssh_key + +short_description: Create and manage ssh keys on the Hetzner Cloud. + + +description: + - Create, update and manage ssh keys on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud ssh_key to manage. + - Only required if no ssh_key I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud ssh_key to manage. + - Only required if no ssh_key I(id) is given or a ssh_key does not exist. + type: str + fingerprint: + description: + - The Fingerprint of the Hetzner Cloud ssh_key to manage. + - Only required if no ssh_key I(id) or I(name) is given. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + public_key: + description: + - The Public Key to add. + - Required if ssh_key does not exist. + type: str + state: + description: + - State of the ssh_key. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Create a basic ssh_key + hetzner.hcloud.ssh_key: + name: my-ssh_key + public_key: ssh-rsa AAAjjk76kgf...Xt + state: present + +- name: Create a ssh_key with labels + hetzner.hcloud.ssh_key: + name: my-ssh_key + public_key: ssh-rsa AAAjjk76kgf...Xt + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the ssh_key is absent (remove if needed) + hetzner.hcloud.ssh_key: + name: my-ssh_key + state: absent +""" + +RETURN = """ +hcloud_ssh_key: + description: The ssh_key instance + returned: Always + type: complex + contains: + id: + description: ID of the ssh_key + type: int + returned: Always + sample: 12345 + name: + description: Name of the ssh_key + type: str + returned: Always + sample: my-ssh-key + fingerprint: + description: Fingerprint of the ssh_key + type: str + returned: Always + sample: b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f + public_key: + description: Public key of the ssh_key + type: str + returned: Always + sample: "ssh-rsa AAAjjk76kgf...Xt" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.ssh_keys import BoundSSHKey + + +class AnsibleHCloudSSHKey(AnsibleHCloud): + represent = "hcloud_ssh_key" + + hcloud_ssh_key: BoundSSHKey | None = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_ssh_key.id), + "name": to_native(self.hcloud_ssh_key.name), + "fingerprint": to_native(self.hcloud_ssh_key.fingerprint), + "public_key": to_native(self.hcloud_ssh_key.public_key), + "labels": self.hcloud_ssh_key.labels, + } + + def _get_ssh_key(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_ssh_key = self.client.ssh_keys.get_by_id(self.module.params.get("id")) + elif self.module.params.get("fingerprint") is not None: + self.hcloud_ssh_key = self.client.ssh_keys.get_by_fingerprint(self.module.params.get("fingerprint")) + elif self.module.params.get("name") is not None: + self.hcloud_ssh_key = self.client.ssh_keys.get_by_name(self.module.params.get("name")) + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_ssh_key(self): + self.module.fail_on_missing_params(required_params=["name", "public_key"]) + params = { + "name": self.module.params.get("name"), + "public_key": self.module.params.get("public_key"), + "labels": self.module.params.get("labels"), + } + + if not self.module.check_mode: + try: + self.client.ssh_keys.create(**params) + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_ssh_key() + + def _update_ssh_key(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_ssh_key.name != name: + self.module.fail_on_missing_params(required_params=["id"]) + if not self.module.check_mode: + self.hcloud_ssh_key.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_ssh_key.labels != labels: + if not self.module.check_mode: + self.hcloud_ssh_key.update(labels=labels) + self._mark_as_changed() + + self._get_ssh_key() + + def present_ssh_key(self): + self._get_ssh_key() + if self.hcloud_ssh_key is None: + self._create_ssh_key() + else: + self._update_ssh_key() + + def delete_ssh_key(self): + self._get_ssh_key() + if self.hcloud_ssh_key is not None: + if not self.module.check_mode: + try: + self.client.ssh_keys.delete(self.hcloud_ssh_key) + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_ssh_key = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + public_key={"type": "str"}, + fingerprint={"type": "str"}, + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name", "fingerprint"]], + required_if=[["state", "present", ["name"]]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudSSHKey.define_module() + + hcloud = AnsibleHCloudSSHKey(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_ssh_key() + elif state == "present": + hcloud.present_ssh_key() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/ssh_key_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/ssh_key_info.py new file mode 100644 index 000000000..7a4ab5928 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/ssh_key_info.py @@ -0,0 +1,156 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: ssh_key_info +short_description: Gather infos about your Hetzner Cloud ssh_keys. +description: + - Gather facts about your Hetzner Cloud ssh_keys. +author: + - Christopher Schmitt (@cschmitt-hcloud) +options: + id: + description: + - The ID of the ssh key you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the ssh key you want to get. + type: str + fingerprint: + description: + - The fingerprint of the ssh key you want to get. + type: str + label_selector: + description: + - The label selector for the ssh key you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud sshkey infos + hetzner.hcloud.ssh_key_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_ssh_key_info +""" + +RETURN = """ +hcloud_ssh_key_info: + description: The ssh key instances + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the ssh_key + returned: always + type: int + sample: 1937415 + name: + description: Name of the ssh_key + returned: always + type: str + sample: my-ssh-key + fingerprint: + description: Fingerprint of the ssh key + returned: always + type: str + sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3 + public_key: + description: The actual public key + returned: always + type: str + sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.ssh_keys import BoundSSHKey + + +class AnsibleHCloudSSHKeyInfo(AnsibleHCloud): + represent = "hcloud_ssh_key_info" + + hcloud_ssh_key_info: list[BoundSSHKey] | None = None + + def _prepare_result(self): + ssh_keys = [] + + for ssh_key in self.hcloud_ssh_key_info: + if ssh_key: + ssh_keys.append( + { + "id": to_native(ssh_key.id), + "name": to_native(ssh_key.name), + "fingerprint": to_native(ssh_key.fingerprint), + "public_key": to_native(ssh_key.public_key), + "labels": ssh_key.labels, + } + ) + return ssh_keys + + def get_ssh_keys(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("fingerprint") is not None: + self.hcloud_ssh_key_info = [ + self.client.ssh_keys.get_by_fingerprint(self.module.params.get("fingerprint")) + ] + elif self.module.params.get("label_selector") is not None: + self.hcloud_ssh_key_info = self.client.ssh_keys.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_ssh_key_info = self.client.ssh_keys.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + fingerprint={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudSSHKeyInfo.define_module() + hcloud = AnsibleHCloudSSHKeyInfo(module) + + hcloud.get_ssh_keys() + result = hcloud.get_result() + + ansible_info = {"hcloud_ssh_key_info": result["hcloud_ssh_key_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/subnetwork.py b/ansible_collections/hetzner/hcloud/plugins/modules/subnetwork.py new file mode 100644 index 000000000..aea40bb13 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/subnetwork.py @@ -0,0 +1,236 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: subnetwork + +short_description: Manage cloud subnetworks on the Hetzner Cloud. + + +description: + - Create, update and delete cloud subnetworks on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - The name or ID of the Hetzner Cloud Networks. + type: str + required: true + ip_range: + description: + - IP range of the subnetwork. + type: str + required: true + type: + description: + - Type of subnetwork. + type: str + choices: [ server, cloud, vswitch ] + required: true + network_zone: + description: + - Name of network zone. + type: str + required: true + vswitch_id: + description: + - ID of the vSwitch you want to couple with your Network. + - Required if type == vswitch + type: int + state: + description: + - State of the subnetwork. + default: present + choices: [ absent, present ] + type: str + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic subnetwork + hetzner.hcloud.subnetwork: + network: my-network + ip_range: 10.0.0.0/16 + network_zone: eu-central + type: cloud + state: present + +- name: Create a basic subnetwork + hetzner.hcloud.subnetwork: + network: my-vswitch-network + ip_range: 10.0.0.0/24 + network_zone: eu-central + type: vswitch + vswitch_id: 123 + state: present + +- name: Ensure the subnetwork is absent (remove if needed) + hetzner.hcloud.subnetwork: + network: my-network + ip_range: 10.0.0.0/8 + network_zone: eu-central + type: cloud + state: absent +""" + +RETURN = """ +hcloud_subnetwork: + description: One Subnet of a Network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + ip_range: + description: IP range of the Network + type: str + returned: always + sample: 10.0.0.0/8 + type: + description: Type of subnetwork + type: str + returned: always + sample: server + network_zone: + description: Name of network zone + type: str + returned: always + sample: eu-central + vswitch_id: + description: ID of the vswitch, null if not type vswitch + type: int + returned: always + sample: 123 + gateway: + description: Gateway of the subnetwork + type: str + returned: always + sample: 10.0.0.1 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.networks import BoundNetwork, NetworkSubnet + + +class AnsibleHCloudSubnetwork(AnsibleHCloud): + represent = "hcloud_subnetwork" + + hcloud_network: BoundNetwork | None = None + hcloud_subnetwork: NetworkSubnet | None = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "ip_range": to_native(self.hcloud_subnetwork.ip_range), + "type": to_native(self.hcloud_subnetwork.type), + "network_zone": to_native(self.hcloud_subnetwork.network_zone), + "gateway": self.hcloud_subnetwork.gateway, + "vswitch_id": self.hcloud_subnetwork.vswitch_id, + } + + def _get_network(self): + try: + self.hcloud_network = self._client_get_by_name_or_id( + "networks", + self.module.params.get("network"), + ) + self.hcloud_subnetwork = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _get_subnetwork(self): + subnet_ip_range = self.module.params.get("ip_range") + for subnetwork in self.hcloud_network.subnets: + if subnetwork.ip_range == subnet_ip_range: + self.hcloud_subnetwork = subnetwork + + def _create_subnetwork(self): + params = { + "ip_range": self.module.params.get("ip_range"), + "type": self.module.params.get("type"), + "network_zone": self.module.params.get("network_zone"), + } + if self.module.params.get("type") == NetworkSubnet.TYPE_VSWITCH: + self.module.fail_on_missing_params(required_params=["vswitch_id"]) + params["vswitch_id"] = self.module.params.get("vswitch_id") + + if not self.module.check_mode: + try: + self.hcloud_network.add_subnet(subnet=NetworkSubnet(**params)).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + self._mark_as_changed() + self._get_network() + self._get_subnetwork() + + def present_subnetwork(self): + self._get_network() + self._get_subnetwork() + if self.hcloud_subnetwork is None: + self._create_subnetwork() + + def delete_subnetwork(self): + self._get_network() + self._get_subnetwork() + if self.hcloud_subnetwork is not None and self.hcloud_network is not None: + if not self.module.check_mode: + try: + self.hcloud_network.delete_subnet(self.hcloud_subnetwork).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self.hcloud_subnetwork = None + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + network_zone={"type": "str", "required": True}, + type={"type": "str", "required": True, "choices": ["server", "cloud", "vswitch"]}, + ip_range={"type": "str", "required": True}, + vswitch_id={"type": "int"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudSubnetwork.define_module() + + hcloud = AnsibleHCloudSubnetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_subnetwork() + elif state == "present": + hcloud.present_subnetwork() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/volume.py b/ansible_collections/hetzner/hcloud/plugins/modules/volume.py new file mode 100644 index 000000000..8442ed90b --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/volume.py @@ -0,0 +1,331 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: volume + +short_description: Create and manage block Volume on the Hetzner Cloud. + + +description: + - Create, update and attach/detach block Volume on the Hetzner Cloud. + +author: + - Christopher Schmitt (@cschmitt-hcloud) + +options: + id: + description: + - The ID of the Hetzner Cloud Block Volume to manage. + - Only required if no volume I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud Block Volume to manage. + - Only required if no volume I(id) is given or a volume does not exist. + type: str + size: + description: + - The size of the Block Volume in GB. + - Required if volume does not yet exists. + type: int + automount: + description: + - Automatically mount the Volume. + type: bool + default: False + format: + description: + - Automatically Format the volume on creation + - Can only be used in case the Volume does not exist. + type: str + choices: [xfs, ext4] + location: + description: + - Location of the Hetzner Cloud Volume. + - Required if no I(server) is given and Volume does not exist. + type: str + server: + description: + - Server Name the Volume should be assigned to. + - Required if no I(location) is given and Volume does not exist. + type: str + delete_protection: + description: + - Protect the Volume for deletion. + type: bool + labels: + description: + - User-defined key-value pairs. + type: dict + state: + description: + - State of the Volume. + default: present + choices: [absent, present] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Create a Volume + hetzner.hcloud.volume: + name: my-volume + location: fsn1 + size: 100 + state: present +- name: Create a Volume and format it with ext4 + hetzner.hcloud.volume: + name: my-volume + location: fsn + format: ext4 + size: 100 + state: present +- name: Mount a existing Volume and automount + hetzner.hcloud.volume: + name: my-volume + server: my-server + automount: true + state: present +- name: Mount a existing Volume and automount + hetzner.hcloud.volume: + name: my-volume + server: my-server + automount: true + state: present +- name: Ensure the Volume is absent (remove if needed) + hetzner.hcloud.volume: + name: my-volume + state: absent +""" + +RETURN = """ +hcloud_volume: + description: The block Volume + returned: Always + type: complex + contains: + id: + description: ID of the Volume + type: int + returned: Always + sample: 12345 + name: + description: Name of the Volume + type: str + returned: Always + sample: my-volume + size: + description: Size in GB of the Volume + type: int + returned: Always + sample: 1337 + linux_device: + description: Path to the device that contains the Volume. + returned: always + type: str + sample: /dev/disk/by-id/scsi-0HC_Volume_12345 + version_added: "0.1.0" + location: + description: Location name where the Volume is located at + type: str + returned: Always + sample: "fsn1" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 + server: + description: Server name where the Volume is attached to + type: str + returned: Always + sample: "my-server" + delete_protection: + description: True if Volume is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.volumes import BoundVolume + + +class AnsibleHCloudVolume(AnsibleHCloud): + represent = "hcloud_volume" + + hcloud_volume: BoundVolume | None = None + + def _prepare_result(self): + server_name = None + if self.hcloud_volume.server is not None: + server_name = to_native(self.hcloud_volume.server.name) + + return { + "id": to_native(self.hcloud_volume.id), + "name": to_native(self.hcloud_volume.name), + "size": self.hcloud_volume.size, + "location": to_native(self.hcloud_volume.location.name), + "labels": self.hcloud_volume.labels, + "server": server_name, + "linux_device": to_native(self.hcloud_volume.linux_device), + "delete_protection": self.hcloud_volume.protection["delete"], + } + + def _get_volume(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_volume = self.client.volumes.get_by_id(self.module.params.get("id")) + else: + self.hcloud_volume = self.client.volumes.get_by_name(self.module.params.get("name")) + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def _create_volume(self): + self.module.fail_on_missing_params(required_params=["name", "size"]) + params = { + "name": self.module.params.get("name"), + "size": self.module.params.get("size"), + "automount": self.module.params.get("automount"), + "format": self.module.params.get("format"), + "labels": self.module.params.get("labels"), + } + if self.module.params.get("server") is not None: + params["server"] = self.client.servers.get_by_name(self.module.params.get("server")) + elif self.module.params.get("location") is not None: + params["location"] = self.client.locations.get_by_name(self.module.params.get("location")) + else: + self.module.fail_json(msg="server or location is required") + + if not self.module.check_mode: + try: + resp = self.client.volumes.create(**params) + resp.action.wait_until_finished() + [action.wait_until_finished() for action in resp.next_actions] + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_volume() + self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() + except HCloudException as exception: + self.fail_json_hcloud(exception) + self._mark_as_changed() + self._get_volume() + + def _update_volume(self): + try: + size = self.module.params.get("size") + if size: + if self.hcloud_volume.size < size: + if not self.module.check_mode: + self.hcloud_volume.resize(size).wait_until_finished() + self._mark_as_changed() + elif self.hcloud_volume.size > size: + self.module.warn("Shrinking of volumes is not supported") + + server_name = self.module.params.get("server") + if server_name: + server = self.client.servers.get_by_name(server_name) + if self.hcloud_volume.server is None or self.hcloud_volume.server.name != server.name: + if not self.module.check_mode: + automount = self.module.params.get("automount", False) + self.hcloud_volume.attach(server, automount=automount).wait_until_finished() + self._mark_as_changed() + else: + if self.hcloud_volume.server is not None: + if not self.module.check_mode: + self.hcloud_volume.detach().wait_until_finished() + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_volume.labels: + if not self.module.check_mode: + self.hcloud_volume.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_volume.protection["delete"]: + if not self.module.check_mode: + self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_volume() + except HCloudException as exception: + self.fail_json_hcloud(exception) + + def present_volume(self): + self._get_volume() + if self.hcloud_volume is None: + self._create_volume() + else: + self._update_volume() + + def delete_volume(self): + try: + self._get_volume() + if self.hcloud_volume is not None: + if not self.module.check_mode: + if self.hcloud_volume.server is not None: + self.hcloud_volume.detach().wait_until_finished() + self.client.volumes.delete(self.hcloud_volume) + self._mark_as_changed() + self.hcloud_volume = None + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + size={"type": "int"}, + location={"type": "str"}, + server={"type": "str"}, + labels={"type": "dict"}, + automount={"type": "bool", "default": False}, + format={"type": "str", "choices": ["xfs", "ext4"]}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **super().base_module_arguments(), + ), + required_one_of=[["id", "name"]], + mutually_exclusive=[["location", "server"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudVolume.define_module() + + hcloud = AnsibleHCloudVolume(module) + state = module.params.get("state") + if state == "absent": + module.fail_on_missing_params(required_params=["name"]) + hcloud.delete_volume() + else: + hcloud.present_volume() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/volume_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/volume_info.py new file mode 100644 index 000000000..1e507690e --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/volume_info.py @@ -0,0 +1,174 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import annotations + +DOCUMENTATION = """ +--- +module: volume_info + +short_description: Gather infos about your Hetzner Cloud Volumes. + +description: + - Gather infos about your Hetzner Cloud Volumes. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Volume you want to get. + - The module will fail if the provided ID is invalid. + type: int + name: + description: + - The name of the Volume you want to get. + type: str + label_selector: + description: + - The label selector for the Volume you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +""" + +EXAMPLES = """ +- name: Gather hcloud Volume infos + hetzner.hcloud.volume_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_volume_info +""" + +RETURN = """ +hcloud_volume_info: + description: The Volume infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Volume + returned: always + type: int + sample: 1937415 + name: + description: Name of the Volume + returned: always + type: str + sample: my-volume + size: + description: Size of the Volume + returned: always + type: str + sample: 10 + linux_device: + description: Path to the device that contains the Volume. + returned: always + type: str + sample: /dev/disk/by-id/scsi-0HC_Volume_12345 + version_added: "0.1.0" + location: + description: Name of the location where the Volume resides in + returned: always + type: str + sample: fsn1 + server: + description: Name of the server where the Volume is attached to + returned: always + type: str + sample: my-server + delete_protection: + description: True if the Volume is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.text.converters import to_native + +from ..module_utils.hcloud import AnsibleHCloud +from ..module_utils.vendor.hcloud import HCloudException +from ..module_utils.vendor.hcloud.volumes import BoundVolume + + +class AnsibleHCloudVolumeInfo(AnsibleHCloud): + represent = "hcloud_volume_info" + + hcloud_volume_info: list[BoundVolume] | None = None + + def _prepare_result(self): + tmp = [] + + for volume in self.hcloud_volume_info: + if volume is not None: + server_name = None + if volume.server is not None: + server_name = to_native(volume.server.name) + tmp.append( + { + "id": to_native(volume.id), + "name": to_native(volume.name), + "size": volume.size, + "location": to_native(volume.location.name), + "labels": volume.labels, + "server": server_name, + "linux_device": to_native(volume.linux_device), + "delete_protection": volume.protection["delete"], + } + ) + + return tmp + + def get_volumes(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_volume_info = [self.client.volumes.get_by_id(self.module.params.get("id"))] + elif self.module.params.get("name") is not None: + self.hcloud_volume_info = [self.client.volumes.get_by_name(self.module.params.get("name"))] + elif self.module.params.get("label_selector") is not None: + self.hcloud_volume_info = self.client.volumes.get_all( + label_selector=self.module.params.get("label_selector") + ) + else: + self.hcloud_volume_info = self.client.volumes.get_all() + + except HCloudException as exception: + self.fail_json_hcloud(exception) + + @classmethod + def define_module(cls): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **super().base_module_arguments(), + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHCloudVolumeInfo.define_module() + hcloud = AnsibleHCloudVolumeInfo(module) + + hcloud.get_volumes() + result = hcloud.get_result() + + ansible_info = {"hcloud_volume_info": result["hcloud_volume_info"]} + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() -- cgit v1.2.3