diff --git a/changelogs/fragments/txt-record-filter.yml b/changelogs/fragments/txt-record-filter.yml new file mode 100644 index 0000000..e58c383 --- /dev/null +++ b/changelogs/fragments/txt-record-filter.yml @@ -0,0 +1,2 @@ +minor_changes: + - txt_record - Add new txt_record filter to help format TXT , e.g. ``"{{ 'v=spf1 include:_spf.example.net ~all' | hetzner.hcloud.txt_record }}"``. diff --git a/plugins/filter/all.py b/plugins/filter/all.py index 2981311..6dea3f8 100644 --- a/plugins/filter/all.py +++ b/plugins/filter/all.py @@ -47,6 +47,25 @@ def load_balancer_status(load_balancer: dict, *args, **kwargs) -> Literal["unkno raise AnsibleFilterError(f"load_balancer_status - {to_native(exc)}", orig_exc=exc) from exc +# pylint: disable=unused-argument +def txt_record(record: str, *args, **kwargs) -> str: + """ + Return the status of a Load Balancer based on its targets. + """ + try: + record = record.replace('"', '\\"') + + parts = [] + for start in range(0, len(record), 255): + end = min(start + 255, len(record)) + parts.append('"' + record[start:end] + '"') + record = " ".join(parts) + + return record + except Exception as exc: + raise AnsibleFilterError(f"txt_record - {to_native(exc)}", orig_exc=exc) from exc + + class FilterModule: """ Hetzner Cloud filters. @@ -55,4 +74,5 @@ class FilterModule: def filters(self): return { "load_balancer_status": load_balancer_status, + "txt_record": txt_record, } diff --git a/plugins/filter/txt_record.yml b/plugins/filter/txt_record.yml new file mode 100644 index 0000000..1041959 --- /dev/null +++ b/plugins/filter/txt_record.yml @@ -0,0 +1,19 @@ +DOCUMENTATION: + name: txt_record + version_added: 6.1.0 + short_description: Format a TXT record + description: + - Format a TXT record by splitting it in quoted strings of 255 characters. + options: + _input: + description: Record value to format. + type: string + required: true +EXAMPLES: | + # Format a TXT record + {{ 'v=spf1 include:_spf.example.net ~all' | hetzner.hcloud.txt_record }} + +RETURN: + _value: + description: The formatted TXT record. + type: string diff --git a/plugins/modules/zone_rrset.py b/plugins/modules/zone_rrset.py index 895315f..aba1e86 100644 --- a/plugins/modules/zone_rrset.py +++ b/plugins/modules/zone_rrset.py @@ -93,6 +93,15 @@ EXAMPLES = """ comment: web server 2 state: present +- name: Create a TXT record + hetzner.hcloud.zone_rrset: + zone: example.com + name: "@" + type: "TXT" + records: + - value: "{{ 'v=spf1 include:_spf.example.net ~all' | hetzner.hcloud.txt_record }}" + state: present + - name: Delete a Zone RRSet hetzner.hcloud.zone_rrset: zone: 42 diff --git a/tests/unit/filter/test_all.py b/tests/unit/filter/test_all.py index 1e1be07..ea3232e 100644 --- a/tests/unit/filter/test_all.py +++ b/tests/unit/filter/test_all.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -from plugins.filter.all import load_balancer_status +from plugins.filter.all import load_balancer_status, txt_record def _lb_target_server(status: str) -> dict: @@ -43,3 +43,18 @@ LOAD_BALANCER_STATUS_TEST_CASES = ( @pytest.mark.parametrize(("value", "expected"), LOAD_BALANCER_STATUS_TEST_CASES) def test_load_balancer_status(value, expected): assert expected == load_balancer_status(value) + + +manyA = "a" * 255 +someB = "b" * 10 + +TXT_RECORD_TEST_CASES = ( + ("hello world", '"hello world"'), + ('hello "world"', '"hello \\"world\\""'), + (manyA + someB, f'"{manyA}" "{someB}"'), +) + + +@pytest.mark.parametrize(("value", "expected"), TXT_RECORD_TEST_CASES) +def test_txt_record(value, expected): + assert expected == txt_record(value)