summaryrefslogtreecommitdiffstats
path: root/ansible_collections/hetzner/hcloud/plugins/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/hetzner/hcloud/plugins/modules
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/hetzner/hcloud/plugins/modules')
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py291
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py162
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py160
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py160
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py359
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py355
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py185
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py185
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py219
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py219
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py318
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py398
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py209
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py620
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py321
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py158
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py159
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py159
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py243
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py293
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py230
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py271
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py360
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py196
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py928
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py244
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py244
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py246
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py183
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py183
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py243
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py169
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py169
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py247
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py340
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py186
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py186
38 files changed, 9798 insertions, 0 deletions
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py b/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py
new file mode 100644
index 000000000..0f6dcf0f2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py
@@ -0,0 +1,291 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..855706f1f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..8cebabf8c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..8cebabf8c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..34608977e
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py
@@ -0,0 +1,359 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..1ee61ea13
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py
@@ -0,0 +1,355 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..2ec359600
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py
@@ -0,0 +1,185 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..2ec359600
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py
@@ -0,0 +1,185 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..8acd8846a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..8acd8846a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..9c6c2bbaf
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py
@@ -0,0 +1,318 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..159dad258
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py
@@ -0,0 +1,398 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..63a7c5471
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py
@@ -0,0 +1,209 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..b5edcc6b5
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py
@@ -0,0 +1,620 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..760884466
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py
@@ -0,0 +1,321 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..a481ea9c9
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..623c6ab68
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py
@@ -0,0 +1,159 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..623c6ab68
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py
@@ -0,0 +1,159 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..9c005d29f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..382e447aa
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py
@@ -0,0 +1,293 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..522bb679d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..c192d5fec
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py
@@ -0,0 +1,271 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..9f79fbe70
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..c75177953
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py
@@ -0,0 +1,196 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..3a77da695
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py
@@ -0,0 +1,928 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..102ceec0d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py
@@ -0,0 +1,244 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..102ceec0d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py
@@ -0,0 +1,244 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..79f6838fd
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..a84067c32
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..a84067c32
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..59a5197f5
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..aab98ed60
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py
@@ -0,0 +1,169 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..aab98ed60
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py
@@ -0,0 +1,169 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..c2ba66d80
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py
@@ -0,0 +1,247 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..623a399b4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..9520bfa14
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
new file mode 100644
index 000000000..9520bfa14
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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()