ansible-collection-hetzner-.../plugins/modules/certificate.py
Jonas L 4bb02b9cd2
refactor: remove unneeded to_native calls (#477)
##### SUMMARY

Since we only support python >=3.8, the `to_native` python2/3
compatibility function is not needed anymore. We only keep it for
printing a traceback free exception message. Where a type conversion is
needed, we use the built-in `str()` function.
2024-03-27 14:11:30 +01:00

288 lines
9.6 KiB
Python

#!/usr/bin/python
# 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 annotations
DOCUMENTATION = """
---
module: certificate
short_description: Create and manage certificates on the Hetzner Cloud.
description:
- Create, update and manage certificates on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
id:
description:
- The ID of the Hetzner Cloud certificate to manage.
- Only required if no certificate I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud certificate to manage.
- Only required if no certificate I(id) is given or a certificate does not exist.
type: str
labels:
description:
- User-defined labels (key-value pairs)
type: dict
certificate:
description:
- Certificate and chain in PEM format, in order so that each record directly certifies the one preceding.
- Required if certificate does not exist and I(type=uploaded).
type: str
private_key:
description:
- Certificate key in PEM format.
- Required if certificate does not exist and I(type=uploaded).
type: str
domain_names:
description:
- Domains and subdomains that should be contained in the Certificate issued by Let's Encrypt.
- Required if I(type=managed).
type: list
default: [ ]
elements: str
type:
description:
- Choose between uploading a Certificate in PEM format or requesting a managed Let's Encrypt Certificate.
default: uploaded
choices: [ uploaded, managed ]
type: str
state:
description:
- State of the certificate.
default: present
choices: [ absent, present ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
"""
EXAMPLES = """
- name: Create a basic certificate
hetzner.hcloud.certificate:
name: my-certificate
certificate: -----BEGIN CERTIFICATE-----...
private_key: -----BEGIN PRIVATE KEY-----...
state: present
- name: Create a certificate with labels
hetzner.hcloud.certificate:
name: my-certificate
certificate: -----BEGIN CERTIFICATE-----...
private_key: -----BEGIN PRIVATE KEY-----...
labels:
key: value
mylabel: 123
state: present
- name: Create a managed certificate
hetzner.hcloud.certificate:
name: my-certificate
type: managed
domain_names:
- example.com
- www.example.com
state: present
- name: Ensure the certificate is absent (remove if needed)
hetzner.hcloud.certificate:
name: my-certificate
state: absent
"""
RETURN = """
hcloud_certificate:
description: The certificate instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the certificate
returned: always
type: int
sample: 1937415
name:
description: Name of the certificate
returned: always
type: str
sample: my website cert
fingerprint:
description: Fingerprint of the certificate
returned: always
type: str
sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f"
certificate:
description: Certificate and chain in PEM format
returned: always
type: str
sample: "-----BEGIN CERTIFICATE-----..."
domain_names:
description: List of Domains and Subdomains covered by the Certificate
returned: always
type: dict
not_valid_before:
description: Point in time when the Certificate becomes valid (in ISO-8601 format)
returned: always
type: str
not_valid_after:
description: Point in time when the Certificate stops being valid (in ISO-8601 format)
returned: always
type: str
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ..module_utils.hcloud import AnsibleHCloud
from ..module_utils.vendor.hcloud import HCloudException
from ..module_utils.vendor.hcloud.certificates import BoundCertificate
class AnsibleHCloudCertificate(AnsibleHCloud):
represent = "hcloud_certificate"
hcloud_certificate: BoundCertificate | None = None
def _prepare_result(self):
return {
"id": str(self.hcloud_certificate.id),
"name": self.hcloud_certificate.name,
"type": self.hcloud_certificate.type,
"fingerprint": self.hcloud_certificate.fingerprint,
"certificate": self.hcloud_certificate.certificate,
"not_valid_before": self.hcloud_certificate.not_valid_before.isoformat(),
"not_valid_after": self.hcloud_certificate.not_valid_after.isoformat(),
"domain_names": self.hcloud_certificate.domain_names,
"labels": self.hcloud_certificate.labels,
}
def _get_certificate(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_certificate = self.client.certificates.get_by_id(self.module.params.get("id"))
elif self.module.params.get("name") is not None:
self.hcloud_certificate = self.client.certificates.get_by_name(self.module.params.get("name"))
except HCloudException as exception:
self.fail_json_hcloud(exception)
def _create_certificate(self):
self.module.fail_on_missing_params(required_params=["name"])
params = {
"name": self.module.params.get("name"),
"labels": self.module.params.get("labels"),
}
if self.module.params.get("type") == "uploaded":
self.module.fail_on_missing_params(required_params=["certificate", "private_key"])
params["certificate"] = self.module.params.get("certificate")
params["private_key"] = self.module.params.get("private_key")
if not self.module.check_mode:
try:
self.client.certificates.create(**params)
except HCloudException as exception:
self.fail_json_hcloud(exception)
else:
self.module.fail_on_missing_params(required_params=["domain_names"])
params["domain_names"] = self.module.params.get("domain_names")
if not self.module.check_mode:
try:
resp = self.client.certificates.create_managed(**params)
resp.action.wait_until_finished(max_retries=1000)
except HCloudException as exception:
self.fail_json_hcloud(exception)
self._mark_as_changed()
self._get_certificate()
def _update_certificate(self):
try:
name = self.module.params.get("name")
if name is not None and self.hcloud_certificate.name != name:
self.module.fail_on_missing_params(required_params=["id"])
if not self.module.check_mode:
self.hcloud_certificate.update(name=name)
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and self.hcloud_certificate.labels != labels:
if not self.module.check_mode:
self.hcloud_certificate.update(labels=labels)
self._mark_as_changed()
except HCloudException as exception:
self.fail_json_hcloud(exception)
self._get_certificate()
def present_certificate(self):
self._get_certificate()
if self.hcloud_certificate is None:
self._create_certificate()
else:
self._update_certificate()
def delete_certificate(self):
self._get_certificate()
if self.hcloud_certificate is not None:
if not self.module.check_mode:
try:
self.client.certificates.delete(self.hcloud_certificate)
except HCloudException as exception:
self.fail_json_hcloud(exception)
self._mark_as_changed()
self.hcloud_certificate = None
@classmethod
def define_module(cls):
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
type={
"choices": ["uploaded", "managed"],
"default": "uploaded",
},
domain_names={"type": "list", "elements": "str", "default": []},
certificate={"type": "str"},
private_key={"type": "str", "no_log": True},
labels={"type": "dict"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**super().base_module_arguments(),
),
required_one_of=[["id", "name"]],
required_if=[["state", "present", ["name"]]],
supports_check_mode=True,
)
def main():
module = AnsibleHCloudCertificate.define_module()
hcloud = AnsibleHCloudCertificate(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_certificate()
elif state == "present":
hcloud.present_certificate()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()