From fb140f27a041be319bdc97165c918ba55c0f2c44 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 10 Jun 2025 10:27:15 -0700 Subject: [PATCH 01/29] Implment Templating processor and prototype Value.template methods --- octodns/record/chunked.py | 5 +++++ octodns/record/target.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/octodns/record/chunked.py b/octodns/record/chunked.py index a07acfd..4af7e85 100644 --- a/octodns/record/chunked.py +++ b/octodns/record/chunked.py @@ -80,3 +80,8 @@ class _ChunkedValue(str): @property def rdata_text(self): return self + + def template(self, params): + if '{' not in self: + return self + return self.__class__(self.format(**params)) diff --git a/octodns/record/target.py b/octodns/record/target.py index 3d6cea7..db1c7c3 100644 --- a/octodns/record/target.py +++ b/octodns/record/target.py @@ -41,6 +41,11 @@ class _TargetValue(str): def rdata_text(self): return self + def template(self, params): + if '{' not in self: + return self + return self.__class__(self.format(**params)) + # # much like _TargetValue, but geared towards multiple values From c8672cbb301c65148746c5c158e9b48c28d291e9 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 10 Jun 2025 10:37:29 -0700 Subject: [PATCH 02/29] Helps if you add the new files --- .../4af5a11fb21842ffb627d5ee4d80fb14.md | 4 + octodns/processor/templating.py | 51 +++++++ tests/test_octodns_processor_templating.py | 125 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 .changelog/4af5a11fb21842ffb627d5ee4d80fb14.md create mode 100644 octodns/processor/templating.py create mode 100644 tests/test_octodns_processor_templating.py diff --git a/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md b/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md new file mode 100644 index 0000000..3b7ed8f --- /dev/null +++ b/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md @@ -0,0 +1,4 @@ +--- +type: minor +--- +Add new Templating proccors \ No newline at end of file diff --git a/octodns/processor/templating.py b/octodns/processor/templating.py new file mode 100644 index 0000000..144264c --- /dev/null +++ b/octodns/processor/templating.py @@ -0,0 +1,51 @@ +# +# +# + +from octodns.processor.base import BaseProcessor + + +class Templating(BaseProcessor): + + def __init__(self, id, *args, **kwargs): + super().__init__(id, *args, **kwargs) + + def process_source_zone(self, desired, sources): + sources = sources or [] + zone_params = { + 'zone_name': desired.decoded_name, + 'zone_decoded_name': desired.decoded_name, + 'zone_encoded_name': desired.name, + 'zone_num_records': len(desired.records), + 'zone_source_ids': ', '.join(s.id for s in sources), + } + + def params(record): + return { + 'record_name': record.decoded_name, + 'record_decoded_name': record.decoded_name, + 'record_encoded_name': record.name, + 'record_fqdn': record.decoded_fqdn, + 'record_decoded_fqdn': record.decoded_fqdn, + 'record_encoded_fqdn': record.fqdn, + 'record_type': record._type, + 'record_ttl': record.ttl, + 'record_source_id': record.source.id if record.source else None, + **zone_params, + } + + for record in desired.records: + if hasattr(record, 'values'): + new_values = [v.template(params(record)) for v in record.values] + if record.values != new_values: + new = record.copy() + new.values = new_values + desired.add_record(new, replace=True) + else: + new_value = record.value.template(params(record)) + if record.value != new_value: + new = record.copy() + new.value = new_value + desired.add_record(new, replace=True) + + return desired diff --git a/tests/test_octodns_processor_templating.py b/tests/test_octodns_processor_templating.py new file mode 100644 index 0000000..80a591a --- /dev/null +++ b/tests/test_octodns_processor_templating.py @@ -0,0 +1,125 @@ +# +# +# + +from unittest import TestCase +from unittest.mock import call, patch + +from octodns.processor.templating import Templating +from octodns.record import Record +from octodns.zone import Zone + + +def _find(zone, name): + return next(r for r in zone.records if r.name == name) + + +class TemplatingTest(TestCase): + def test_cname(self): + templ = Templating('test') + + zone = Zone('unit.tests.', []) + cname = Record.new( + zone, + 'cname', + { + 'type': 'CNAME', + 'ttl': 42, + 'value': '_cname.{zone_name}something.else.', + }, + lenient=True, + ) + zone.add_record(cname) + noop = Record.new( + zone, + 'noop', + { + 'type': 'CNAME', + 'ttl': 42, + 'value': '_noop.nothing_to_do.something.else.', + }, + lenient=True, + ) + zone.add_record(noop) + + got = templ.process_source_zone(zone, None) + cname = _find(got, 'cname') + self.assertEqual('_cname.unit.tests.something.else.', cname.value) + noop = _find(got, 'noop') + self.assertEqual('_noop.nothing_to_do.something.else.', noop.value) + + def test_txt(self): + templ = Templating('test') + + zone = Zone('unit.tests.', []) + txt = Record.new( + zone, + 'txt', + { + 'type': 'TXT', + 'ttl': 42, + 'value': 'There are {zone_num_records} record(s) in {zone_name}', + }, + ) + zone.add_record(txt) + noop = Record.new( + zone, + 'noop', + {'type': 'TXT', 'ttl': 43, 'value': 'Nothing to template here.'}, + ) + zone.add_record(noop) + + got = templ.process_source_zone(zone, None) + txt = _find(got, 'txt') + self.assertEqual('There are 2 record(s) in unit.tests.', txt.values[0]) + noop = _find(got, 'noop') + self.assertEqual('Nothing to template here.', noop.values[0]) + + @patch('octodns.record.TxtValue.template') + def test_params(self, mock_template): + templ = Templating('test') + + class DummySource: + + def __init__(self, id): + self.id = id + + zone = Zone('unit.tests.', []) + record_source = DummySource('record') + txt = Record.new( + zone, + 'txt', + { + 'type': 'TXT', + 'ttl': 42, + 'value': 'There are {zone_num_records} record(s) in {zone_name}', + }, + source=record_source, + ) + zone.add_record(txt) + + templ.process_source_zone( + zone, sources=[record_source, DummySource('other')] + ) + mock_template.assert_called_once() + self.assertEqual( + call( + { + 'record_name': 'txt', + 'record_decoded_name': 'txt', + 'record_encoded_name': 'txt', + 'record_fqdn': 'txt.unit.tests.', + 'record_decoded_fqdn': 'txt.unit.tests.', + 'record_encoded_fqdn': 'txt.unit.tests.', + 'record_type': 'TXT', + 'record_ttl': 42, + 'record_source_id': 'record', + 'zone_name': 'unit.tests.', + 'zone_decoded_name': 'unit.tests.', + 'zone_encoded_name': 'unit.tests.', + 'zone_num_records': 1, + 'zone_source_ids': 'record, other', + } + ), + mock_template.call_args, + ) From cac5995fb351904b1a235506b85ad805512701b7 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 10 Jun 2025 10:38:31 -0700 Subject: [PATCH 03/29] check for changelog entry first --- .git_hooks_pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.git_hooks_pre-commit b/.git_hooks_pre-commit index de3ba67..3b5f6ac 100755 --- a/.git_hooks_pre-commit +++ b/.git_hooks_pre-commit @@ -7,7 +7,7 @@ GIT=$(dirname "$HOOKS") ROOT=$(dirname "$GIT") . "$ROOT/env/bin/activate" +"$ROOT/script/changelog" check "$ROOT/script/lint" "$ROOT/script/format" --check --quiet || (echo "Formatting check failed, run ./script/format" && exit 1) "$ROOT/script/coverage" -"$ROOT/script/changelog" check From b3ba45f6f7b05682ea7274204369307e5f296224 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 10 Jun 2025 11:57:25 -0700 Subject: [PATCH 04/29] wip Value.template functions --- octodns/record/caa.py | 7 +++++++ octodns/record/ds.py | 7 +++++++ octodns/record/ip.py | 5 +++++ octodns/record/loc.py | 3 +++ octodns/record/mx.py | 7 +++++++ octodns/record/naptr.py | 13 +++++++++++++ octodns/record/srv.py | 7 +++++++ octodns/record/sshfp.py | 7 +++++++ octodns/record/svcb.py | 8 ++++++++ octodns/record/target.py | 5 +++++ octodns/record/tlsa.py | 9 +++++++++ octodns/record/urlfwd.py | 8 ++++++++ 12 files changed, 86 insertions(+) diff --git a/octodns/record/caa.py b/octodns/record/caa.py index a9d048e..b02a1f0 100644 --- a/octodns/record/caa.py +++ b/octodns/record/caa.py @@ -87,6 +87,13 @@ class CaaValue(EqualityTupleMixin, dict): def rdata_text(self): return f'{self.flags} {self.tag} {self.value}' + def template(self, params): + if '{' not in self.value: + return self + new = self.__class__(self) + new.value = new.value.format(**params) + return new + def _equality_tuple(self): return (self.flags, self.tag, self.value) diff --git a/octodns/record/ds.py b/octodns/record/ds.py index 2e8bfe4..5cfc569 100644 --- a/octodns/record/ds.py +++ b/octodns/record/ds.py @@ -164,6 +164,13 @@ class DsValue(EqualityTupleMixin, dict): f'{self.key_tag} {self.algorithm} {self.digest_type} {self.digest}' ) + def template(self, params): + if '{' not in self.digest: + return self + new = self.__class__(self) + new.digest = new.digest.format(**params) + return new + def _equality_tuple(self): return (self.key_tag, self.algorithm, self.digest_type, self.digest) diff --git a/octodns/record/ip.py b/octodns/record/ip.py index 6b3fe3c..8ce605b 100644 --- a/octodns/record/ip.py +++ b/octodns/record/ip.py @@ -45,5 +45,10 @@ class _IpValue(str): def rdata_text(self): return self + def template(self, params): + if '{' not in self: + return self + return self.__class__(self.format(**params)) + _IpAddress = _IpValue diff --git a/octodns/record/loc.py b/octodns/record/loc.py index babbb93..59d55c5 100644 --- a/octodns/record/loc.py +++ b/octodns/record/loc.py @@ -305,6 +305,9 @@ class LocValue(EqualityTupleMixin, dict): def rdata_text(self): return f'{self.lat_degrees} {self.lat_minutes} {self.lat_seconds} {self.lat_direction} {self.long_degrees} {self.long_minutes} {self.long_seconds} {self.long_direction} {self.altitude}m {self.size}m {self.precision_horz}m {self.precision_vert}m' + def template(self, params): + return self + def __hash__(self): return hash( ( diff --git a/octodns/record/mx.py b/octodns/record/mx.py index 36b48b5..5b11dc4 100644 --- a/octodns/record/mx.py +++ b/octodns/record/mx.py @@ -101,6 +101,13 @@ class MxValue(EqualityTupleMixin, dict): def rdata_text(self): return f'{self.preference} {self.exchange}' + def template(self, params): + if '{' not in self.exchange: + return self + new = self.__class__(self) + new.exchange = new.exchange.format(**params) + return new + def __hash__(self): return hash((self.preference, self.exchange)) diff --git a/octodns/record/naptr.py b/octodns/record/naptr.py index 5dc9605..6469311 100644 --- a/octodns/record/naptr.py +++ b/octodns/record/naptr.py @@ -138,6 +138,19 @@ class NaptrValue(EqualityTupleMixin, dict): def rdata_text(self): return f'{self.order} {self.preference} {self.flags} {self.service} {self.regexp} {self.replacement}' + def template(self, params): + if ( + '{' not in self.service + and '{' not in self.regexp + and '{' not in self.replacement + ): + return self + new = self.__class__(self) + new.service = new.service.format(**params) + new.regexp = new.regexp.format(**params) + new.replacement = new.replacement.format(**params) + return new + def __hash__(self): return hash(self.__repr__()) diff --git a/octodns/record/srv.py b/octodns/record/srv.py index e885735..9a49c47 100644 --- a/octodns/record/srv.py +++ b/octodns/record/srv.py @@ -135,6 +135,13 @@ class SrvValue(EqualityTupleMixin, dict): def rdata_text(self): return f"{self.priority} {self.weight} {self.port} {self.target}" + def template(self, params): + if '{' not in self.target: + return self + new = self.__class__(self) + new.target = new.target.format(**params) + return new + def __hash__(self): return hash(self.__repr__()) diff --git a/octodns/record/sshfp.py b/octodns/record/sshfp.py index e1c9de0..4665aa8 100644 --- a/octodns/record/sshfp.py +++ b/octodns/record/sshfp.py @@ -105,6 +105,13 @@ class SshfpValue(EqualityTupleMixin, dict): def rdata_text(self): return f'{self.algorithm} {self.fingerprint_type} {self.fingerprint}' + def template(self, params): + if '{' in self.fingerprint: + return self + new = self.__class__(self) + new.fingerprint = new.fingerprint.format(**params) + return new + def __hash__(self): return hash(self.__repr__()) diff --git a/octodns/record/svcb.py b/octodns/record/svcb.py index 399414c..b286d1c 100644 --- a/octodns/record/svcb.py +++ b/octodns/record/svcb.py @@ -287,6 +287,14 @@ class SvcbValue(EqualityTupleMixin, dict): params += f'={svcparamvalue}' return f'{self.svcpriority} {self.targetname}{params}' + def template(self, params): + if '{' not in self.targetname: + return self + new = self.__class__(self) + new.targetname = new.targetname.format(**params) + # TODO: what, if any of the svcparams should be templated + return new + def __hash__(self): return hash(self.__repr__()) diff --git a/octodns/record/target.py b/octodns/record/target.py index db1c7c3..b1d954b 100644 --- a/octodns/record/target.py +++ b/octodns/record/target.py @@ -80,3 +80,8 @@ class _TargetsValue(str): @property def rdata_text(self): return self + + def template(self, params): + if '{' not in self: + return self + return self.__class__(self.format(**params)) diff --git a/octodns/record/tlsa.py b/octodns/record/tlsa.py index ed7b267..904afc8 100644 --- a/octodns/record/tlsa.py +++ b/octodns/record/tlsa.py @@ -136,6 +136,15 @@ class TlsaValue(EqualityTupleMixin, dict): def rdata_text(self): return f'{self.certificate_usage} {self.selector} {self.matching_type} {self.certificate_association_data}' + def template(self, params): + if '{' in self.certificate_association_data: + return self + new = self.__class__(self) + new.certificate_association_data = ( + new.certificate_association_data.format(**params) + ) + return new + def _equality_tuple(self): return ( self.certificate_usage, diff --git a/octodns/record/urlfwd.py b/octodns/record/urlfwd.py index e735725..2d7d0fe 100644 --- a/octodns/record/urlfwd.py +++ b/octodns/record/urlfwd.py @@ -132,6 +132,14 @@ class UrlfwdValue(EqualityTupleMixin, dict): def rdata_text(self): return f'"{self.path}" "{self.target}" {self.code} {self.masking} {self.query}' + def template(self, params): + if '{' not in self.path and '{' not in self.target: + return self + new = self.__class__(self) + new.path = new.path.format(**params) + new.target = new.target.format(**params) + return new + def _equality_tuple(self): return (self.path, self.target, self.code, self.masking, self.query) From dfad2d9656340622d2aaf5506bbedc8f6c6e74b6 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 10 Jun 2025 12:46:11 -0700 Subject: [PATCH 05/29] Tests for Value.template methods --- octodns/record/ip.py | 4 +--- octodns/record/sshfp.py | 2 +- octodns/record/tlsa.py | 2 +- tests/test_octodns_record_caa.py | 17 ++++++++++++++ tests/test_octodns_record_chunked.py | 12 ++++++++++ tests/test_octodns_record_ds.py | 27 +++++++++++++++++++++++ tests/test_octodns_record_ip.py | 8 +++++++ tests/test_octodns_record_loc.py | 23 +++++++++++++++++++ tests/test_octodns_record_mx.py | 13 +++++++++++ tests/test_octodns_record_naptr.py | 33 ++++++++++++++++++++++++++++ tests/test_octodns_record_srv.py | 27 +++++++++++++++++++++++ tests/test_octodns_record_sshfp.py | 21 ++++++++++++++++++ tests/test_octodns_record_svcb.py | 15 +++++++++++++ tests/test_octodns_record_target.py | 32 ++++++++++++++++++++++++++- tests/test_octodns_record_tlsa.py | 29 ++++++++++++++++++++++++ tests/test_octodns_record_urlfwd.py | 30 +++++++++++++++++++++++++ 16 files changed, 289 insertions(+), 6 deletions(-) diff --git a/octodns/record/ip.py b/octodns/record/ip.py index 8ce605b..ab838af 100644 --- a/octodns/record/ip.py +++ b/octodns/record/ip.py @@ -46,9 +46,7 @@ class _IpValue(str): return self def template(self, params): - if '{' not in self: - return self - return self.__class__(self.format(**params)) + return self _IpAddress = _IpValue diff --git a/octodns/record/sshfp.py b/octodns/record/sshfp.py index 4665aa8..e186744 100644 --- a/octodns/record/sshfp.py +++ b/octodns/record/sshfp.py @@ -106,7 +106,7 @@ class SshfpValue(EqualityTupleMixin, dict): return f'{self.algorithm} {self.fingerprint_type} {self.fingerprint}' def template(self, params): - if '{' in self.fingerprint: + if '{' not in self.fingerprint: return self new = self.__class__(self) new.fingerprint = new.fingerprint.format(**params) diff --git a/octodns/record/tlsa.py b/octodns/record/tlsa.py index 904afc8..c6e72e9 100644 --- a/octodns/record/tlsa.py +++ b/octodns/record/tlsa.py @@ -137,7 +137,7 @@ class TlsaValue(EqualityTupleMixin, dict): return f'{self.certificate_usage} {self.selector} {self.matching_type} {self.certificate_association_data}' def template(self, params): - if '{' in self.certificate_association_data: + if '{' not in self.certificate_association_data: return self new = self.__class__(self) new.certificate_association_data = ( diff --git a/tests/test_octodns_record_caa.py b/tests/test_octodns_record_caa.py index 8caecb9..224dd00 100644 --- a/tests/test_octodns_record_caa.py +++ b/tests/test_octodns_record_caa.py @@ -285,3 +285,20 @@ class TestRecordCaa(TestCase): {'type': 'CAA', 'ttl': 600, 'value': {'tag': 'iodef'}}, ) self.assertEqual(['missing value'], ctx.exception.reasons) + + +class TestCaaValue(TestCase): + + def test_template(self): + value = CaaValue( + {'flags': 0, 'tag': 'issue', 'value': 'ca.example.net'} + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = CaaValue( + {'flags': 0, 'tag': 'issue', 'value': 'ca.{needle}.net'} + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('ca.42.net', got.value) diff --git a/tests/test_octodns_record_chunked.py b/tests/test_octodns_record_chunked.py index 4d7bbc0..38b8f0e 100644 --- a/tests/test_octodns_record_chunked.py +++ b/tests/test_octodns_record_chunked.py @@ -115,3 +115,15 @@ class TestChunkedValue(TestCase): sc = self.SmallerChunkedMixin(['0123456789']) self.assertEqual(['"01234567" "89"'], sc.chunked_values) + + def test_template(self): + s = 'this.has.no.templating.' + value = _ChunkedValue(s) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + s = 'this.does.{needle}.have.templating.' + value = _ChunkedValue(s) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('this.does.42.have.templating.', got) diff --git a/tests/test_octodns_record_ds.py b/tests/test_octodns_record_ds.py index f0429de..83fea42 100644 --- a/tests/test_octodns_record_ds.py +++ b/tests/test_octodns_record_ds.py @@ -259,3 +259,30 @@ class TestRecordDs(TestCase): self.assertEqual(DsValue(values[1]), a.values[1].data) self.assertEqual('1 2 3 99148c44', a.values[1].rdata_text) self.assertEqual('1 2 3 99148c44', a.values[1].__repr__()) + + +class TestDsValue(TestCase): + + def test_template(self): + value = DsValue( + { + 'key_tag': 0, + 'algorithm': 1, + 'digest_type': 2, + 'digest': 'abcdef0123456', + } + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = DsValue( + { + 'key_tag': 0, + 'algorithm': 1, + 'digest_type': 2, + 'digest': 'abcd{needle}ef0123456', + } + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('abcd42ef0123456', got.digest) diff --git a/tests/test_octodns_record_ip.py b/tests/test_octodns_record_ip.py index f9ba62d..818bca1 100644 --- a/tests/test_octodns_record_ip.py +++ b/tests/test_octodns_record_ip.py @@ -27,3 +27,11 @@ class TestRecordIp(TestCase): zone = Zone('unit.tests.', []) a = ARecord(zone, 'a', {'ttl': 42, 'value': '1.2.3.4'}) self.assertEqual('1.2.3.4', a.values[0].rdata_text) + + +class TestIpValue(TestCase): + + def test_template(self): + value = Ipv4Value('1.2.3.4') + # template is a noop + self.assertIs(value, value.template({'needle': 42})) diff --git a/tests/test_octodns_record_loc.py b/tests/test_octodns_record_loc.py index 278b816..80c9505 100644 --- a/tests/test_octodns_record_loc.py +++ b/tests/test_octodns_record_loc.py @@ -715,3 +715,26 @@ class TestRecordLoc(TestCase): self.assertEqual( ['invalid value for size "99999999.99"'], ctx.exception.reasons ) + + +class TestLocValue(TestCase): + + def test_template(self): + value = LocValue( + { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + } + ) + # loc value template is a noop + self.assertIs(value, value.template({})) diff --git a/tests/test_octodns_record_mx.py b/tests/test_octodns_record_mx.py index a2fba19..57386c4 100644 --- a/tests/test_octodns_record_mx.py +++ b/tests/test_octodns_record_mx.py @@ -268,3 +268,16 @@ class TestRecordMx(TestCase): }, ) self.assertEqual('.', record.values[0].exchange) + + +class TestMxValue(TestCase): + + def test_template(self): + value = MxValue({'preference': 10, 'exchange': 'smtp1.'}) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = MxValue({'preference': 10, 'exchange': 'smtp1.{needle}.'}) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('smtp1.42.', got.exchange) diff --git a/tests/test_octodns_record_naptr.py b/tests/test_octodns_record_naptr.py index b099de4..3c05550 100644 --- a/tests/test_octodns_record_naptr.py +++ b/tests/test_octodns_record_naptr.py @@ -449,3 +449,36 @@ class TestRecordNaptr(TestCase): with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v}) self.assertEqual(['unrecognized flags "X"'], ctx.exception.reasons) + + +class TestNaptrValue(TestCase): + + def test_template(self): + value = NaptrValue( + { + 'order': 10, + 'preference': 11, + 'flags': 'X', + 'service': 'Y', + 'regexp': 'Z', + 'replacement': '.', + } + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = NaptrValue( + { + 'order': 10, + 'preference': 11, + 'flags': 'X', + 'service': 'Y{needle}', + 'regexp': 'Z{needle}', + 'replacement': '.{needle}', + } + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('Y42', got.service) + self.assertEqual('Z42', got.regexp) + self.assertEqual('.42', got.replacement) diff --git a/tests/test_octodns_record_srv.py b/tests/test_octodns_record_srv.py index e525afd..8470a02 100644 --- a/tests/test_octodns_record_srv.py +++ b/tests/test_octodns_record_srv.py @@ -450,3 +450,30 @@ class TestRecordSrv(TestCase): ['Invalid SRV target "100 foo.bar.com." is not a valid FQDN.'], ctx.exception.reasons, ) + + +class TestSrvValue(TestCase): + + def test_template(self): + value = SrvValue( + { + 'priority': 10, + 'weight': 11, + 'port': 12, + 'target': 'no_placeholders', + } + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = SrvValue( + { + 'priority': 10, + 'weight': 11, + 'port': 12, + 'target': 'has_{needle}_placeholder', + } + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('has_42_placeholder', got.target) diff --git a/tests/test_octodns_record_sshfp.py b/tests/test_octodns_record_sshfp.py index 4e66186..7364ec1 100644 --- a/tests/test_octodns_record_sshfp.py +++ b/tests/test_octodns_record_sshfp.py @@ -333,3 +333,24 @@ class TestRecordSshfp(TestCase): }, ) self.assertEqual(['missing fingerprint'], ctx.exception.reasons) + + +class TestSshFpValue(TestCase): + + def test_template(self): + value = SshfpValue( + {'algorithm': 10, 'fingerprint_type': 11, 'fingerprint': 'abc123'} + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = SshfpValue( + { + 'algorithm': 10, + 'fingerprint_type': 11, + 'fingerprint': 'ab{needle}c123', + } + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('ab42c123', got.fingerprint) diff --git a/tests/test_octodns_record_svcb.py b/tests/test_octodns_record_svcb.py index 2d8cecd..5345b70 100644 --- a/tests/test_octodns_record_svcb.py +++ b/tests/test_octodns_record_svcb.py @@ -673,3 +673,18 @@ class TestRecordSvcb(TestCase): ], ctx.exception.reasons, ) + + +class TestSrvValue(TestCase): + + def test_template(self): + value = SvcbValue({'svcpriority': 0, 'targetname': 'foo.example.com.'}) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = SvcbValue( + {'svcpriority': 0, 'targetname': 'foo.{needle}.example.com.'} + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('foo.42.example.com.', got.targetname) diff --git a/tests/test_octodns_record_target.py b/tests/test_octodns_record_target.py index 715cd4a..6700564 100644 --- a/tests/test_octodns_record_target.py +++ b/tests/test_octodns_record_target.py @@ -5,7 +5,7 @@ from unittest import TestCase from octodns.record.alias import AliasRecord -from octodns.record.target import _TargetValue +from octodns.record.target import _TargetsValue, _TargetValue from octodns.zone import Zone @@ -28,3 +28,33 @@ class TestRecordTarget(TestCase): zone = Zone('unit.tests.', []) a = AliasRecord(zone, 'a', {'ttl': 42, 'value': 'some.target.'}) self.assertEqual('some.target.', a.value.rdata_text) + + +class TestTargetValue(TestCase): + + def test_template(self): + s = 'this.has.no.templating.' + value = _TargetValue(s) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + s = 'this.does.{needle}.have.templating.' + value = _TargetValue(s) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('this.does.42.have.templating.', got) + + +class TestTargetsValue(TestCase): + + def test_template(self): + s = 'this.has.no.templating.' + value = _TargetsValue(s) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + s = 'this.does.{needle}.have.templating.' + value = _TargetsValue(s) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('this.does.42.have.templating.', got) diff --git a/tests/test_octodns_record_tlsa.py b/tests/test_octodns_record_tlsa.py index 26132e8..ef29d5d 100644 --- a/tests/test_octodns_record_tlsa.py +++ b/tests/test_octodns_record_tlsa.py @@ -429,3 +429,32 @@ class TestRecordTlsa(TestCase): 'invalid matching_type "{value["matching_type"]}"', ctx.exception.reasons, ) + + +class TestTlsaValue(TestCase): + + def test_template(self): + value = TlsaValue( + { + 'certificate_usage': 1, + 'selector': 1, + 'matching_type': 1, + 'certificate_association_data': 'ABABABABABABABABAB', + } + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = TlsaValue( + { + 'certificate_usage': 1, + 'selector': 1, + 'matching_type': 1, + 'certificate_association_data': 'ABAB{needle}ABABABABABABAB', + } + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual( + 'ABAB42ABABABABABABAB', got.certificate_association_data + ) diff --git a/tests/test_octodns_record_urlfwd.py b/tests/test_octodns_record_urlfwd.py index 6c1f5e1..14f406f 100644 --- a/tests/test_octodns_record_urlfwd.py +++ b/tests/test_octodns_record_urlfwd.py @@ -483,3 +483,33 @@ class TestRecordUrlfwd(TestCase): {'ttl': 32, 'value': UrlfwdValue.parse_rdata_text(rdata)}, ) self.assertEqual(rdata, record.values[0].rdata_text) + + +class TestUrlfwdValue(TestCase): + + def test_template(self): + value = UrlfwdValue( + { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + 'query': 0, + } + ) + got = value.template({'needle': 42}) + self.assertIs(value, got) + + value = UrlfwdValue( + { + 'path': '/{needle}', + 'target': 'http://foo.{needle}', + 'code': 301, + 'masking': 2, + 'query': 0, + } + ) + got = value.template({'needle': 42}) + self.assertIsNot(value, got) + self.assertEqual('/42', got.path) + self.assertEqual('http://foo.42', got.target) From 5dd0815eeda3bb903d5af138bfaca53ec86fff57 Mon Sep 17 00:00:00 2001 From: Ivan Schaller Date: Thu, 12 Jun 2025 22:20:39 +0200 Subject: [PATCH 06/29] Update netbox-dns provider link and netbox-dns plugin link Signed-off-by: Ivan Schaller --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50332aa..92fc88e 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,7 @@ If you have a problem or suggestion, please [open an issue](https://github.com/o - [`doddo/octodns-lexicon`](https://github.com/doddo/octodns-lexicon): Use [Lexicon](https://github.com/AnalogJ/lexicon) providers as octoDNS providers. - [`asyncon/octoblox`](https://github.com/asyncon/octoblox): [Infoblox](https://www.infoblox.com/) provider. - [`sukiyaki/octodns-netbox`](https://github.com/sukiyaki/octodns-netbox): [NetBox](https://github.com/netbox-community/netbox) source. - - [`jcollie/octodns-netbox-dns`](https://github.com/jcollie/octodns-netbox-dns): [NetBox-DNS Plugin](https://github.com/auroraresearchlab/netbox-dns) provider. + - [`olofvndrhr/octodns-netbox-dns`](https://github.com/olofvndrhr/octodns-netbox-dns): [NetBox-DNS Plugin](https://github.com/peteeckel/netbox-plugin-dns) provider. - [`kompetenzbolzen/octodns-custom-provider`](https://github.com/kompetenzbolzen/octodns-custom-provider): zonefile provider & phpIPAM source. - [`Financial-Times/octodns-fastly`](https://github.com/Financial-Times/octodns-fastly): An octoDNS source for Fastly. - [`jvoss/octodns-pihole`](https://github.com/jvoss/octodns-pihole): [Pi-hole](https://pi-hole.net/) provider. From 680fce13fbe6bcb54efc64f9c267282f72cbe99b Mon Sep 17 00:00:00 2001 From: Ivan Schaller Date: Thu, 12 Jun 2025 22:25:26 +0200 Subject: [PATCH 07/29] add official ClouDNS provider to README Signed-off-by: Ivan Schaller --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 92fc88e..09ff2a2 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,8 @@ The table below lists the providers octoDNS supports. They are maintained in the | [UltraDNS](https://vercara.com/authoritative-dns) | [octodns_ultra](https://github.com/octodns/octodns-ultra/) | | | [YamlProvider](/octodns/provider/yaml.py) | built-in | Supports all record types and core functionality | | [deSEC](https://desec.io/) | [octodns_desec](https://github.com/rootshell-labs/octodns-desec) | | +| [ClouDNS](https://www.cloudns.net/) | [octodns_cloudns](https://github.com/ClouDNS/octodns_cloudns) | | + ### Updating to use extracted providers From ba71fad7167d425343eaa0f16c31227b4fc1d0e4 Mon Sep 17 00:00:00 2001 From: Ivan Schaller Date: Fri, 13 Jun 2025 17:43:41 +0200 Subject: [PATCH 08/29] update markdown tables Signed-off-by: Ivan Schaller --- .../44274039f47d4df0946a45fb9c228d3f.md | 4 + README.md | 227 +++++++++--------- 2 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 .changelog/44274039f47d4df0946a45fb9c228d3f.md diff --git a/.changelog/44274039f47d4df0946a45fb9c228d3f.md b/.changelog/44274039f47d4df0946a45fb9c228d3f.md new file mode 100644 index 0000000..e81e06f --- /dev/null +++ b/.changelog/44274039f47d4df0946a45fb9c228d3f.md @@ -0,0 +1,4 @@ +--- +type: none +--- +update markdown tables \ No newline at end of file diff --git a/README.md b/README.md index 09ff2a2..c38c256 100644 --- a/README.md +++ b/README.md @@ -9,37 +9,37 @@ The architecture is pluggable and the tooling is flexible to make it applicable ## Table of Contents -* [Getting started](#getting-started) - * [Workspace](#workspace) - * [Installing a specific commit SHA](#installing-a-specific-commit-sha) - * [Config](#config) - * [Dynamic Zone Config](#dynamic-zone-config) - * [Static Zone Config](#static-zone-config) - * [General Configuration Concepts](#general-configuration-concepts) - * [Quick Example Record](#quick-example-record) - * [Noop](#noop) - * [Making changes](#making-changes) - * [Workflow](#workflow) - * [Bootstrapping config files](#bootstrapping-config-files) -* [Providers](#providers) - * [Updating to use extracted providers](#updating-to-use-extracted-providers) -* [Sources](#sources) - * [Notes](#notes) -* [Processors](#processors) -* [Automatic PTR generation](#automatic-ptr-generation) -* [Compatibility and Compliance](#compatibility-and-compliance) - * [`lenient`](#lenient) - * [`strict_supports`](#strict_supports) - * [Configuring `strict_supports`](#configuring-strict_supports) -* [Custom Sources and Providers](#custom-sources-and-providers) -* [Other Uses](#other-uses) - * [Syncing between providers](#syncing-between-providers) - * [Dynamic sources](#dynamic-sources) -* [Contributing](#contributing) -* [Getting help](#getting-help) -* [Related Projects and Resources](#related-projects-and-resources) -* [License](#license) -* [Authors](#authors) +- [Getting started](#getting-started) + - [Workspace](#workspace) + - [Installing a specific commit SHA](#installing-a-specific-commit-sha) + - [Config](#config) + - [Dynamic Zone Config](#dynamic-zone-config) + - [Static Zone Config](#static-zone-config) + - [General Configuration Concepts](#general-configuration-concepts) + - [Quick Example Record](#quick-example-record) + - [Noop](#noop) + - [Making changes](#making-changes) + - [Workflow](#workflow) + - [Bootstrapping config files](#bootstrapping-config-files) +- [Providers](#providers) + - [Updating to use extracted providers](#updating-to-use-extracted-providers) +- [Sources](#sources) + - [Notes](#notes) +- [Processors](#processors) +- [Automatic PTR generation](#automatic-ptr-generation) +- [Compatibility and Compliance](#compatibility-and-compliance) + - [`lenient`](#lenient) + - [`strict_supports`](#strict_supports) + - [Configuring `strict_supports`](#configuring-strict_supports) +- [Custom Sources and Providers](#custom-sources-and-providers) +- [Other Uses](#other-uses) + - [Syncing between providers](#syncing-between-providers) + - [Dynamic sources](#dynamic-sources) +- [Contributing](#contributing) +- [Getting help](#getting-help) +- [Related Projects and Resources](#related-projects-and-resources) +- [License](#license) +- [Authors](#authors) ## Getting started @@ -63,7 +63,7 @@ $ mkdir config If you'd like to install a version that has not yet been released in a repeatable/safe manner you can do the following. In general octoDNS is fairly stable in between releases thanks to the plan and apply process, but care should be taken regardless. ```console -$ pip install -e git+https://git@github.com/octodns/octodns.git@#egg=octodns +pip install -e git+https://git@github.com/octodns/octodns.git@#egg=octodns ``` ### Config @@ -96,7 +96,7 @@ zones: # This is a dynamic zone config. The source(s), here `config`, will be # queried for a list of zone names and each will dynamically be set up to # match the dynamic entry. - '*': + "*": sources: - config targets: @@ -153,7 +153,6 @@ zones: - config targets: - ns1 - ``` #### General Configuration Concepts @@ -174,7 +173,7 @@ Now that we have something to tell octoDNS about our providers & zones we need t ```yaml --- -'': +"": ttl: 60 type: A values: @@ -268,40 +267,46 @@ It is important to review any `WARNING` log lines printed out during an `octodns The table below lists the providers octoDNS supports. They are maintained in their own repositories and released as independent modules. -| Provider | Module | Notes | -|--|--|--| -| [Akamai Edge DNS](https://www.akamai.com/products/edge-dns) | [octodns_edgedns](https://github.com/octodns/octodns-edgedns/) | | -| [Amazon Route 53](https://aws.amazon.com/route53/) | [octodns_route53](https://github.com/octodns/octodns-route53) | | -| [AutoDNS](https://www.internetx.com/autodns/) | [octodns_autodns](https://github.com/octodns/octodns-autodns) | | -| [Azure DNS](https://azure.microsoft.com/en-us/services/dns/) | [octodns_azure](https://github.com/octodns/octodns-azure/) | | -| [BIND, AXFR, RFC-2136](https://www.isc.org/bind/) | [octodns_bind](https://github.com/octodns/octodns-bind/) | | -| [Cloudflare DNS](https://www.cloudflare.com/dns/) | [octodns_cloudflare](https://github.com/octodns/octodns-cloudflare/) | | -| [Constellix](https://constellix.com/) | [octodns_constellix](https://github.com/octodns/octodns-constellix/) | | -| [DigitalOcean](https://docs.digitalocean.com/products/networking/dns/) | [octodns_digitalocean](https://github.com/octodns/octodns-digitalocean/) | | -| [DNS Made Easy](https://dnsmadeeasy.com/) | [octodns_dnsmadeeasy](https://github.com/octodns/octodns-dnsmadeeasy/) | | -| [DNSimple](https://dnsimple.com/) | [octodns_dnsimple](https://github.com/octodns/octodns-dnsimple/) | | -| [Dyn](https://www.oracle.com/cloud/networking/dns/) ([deprecated](https://www.oracle.com/corporate/acquisitions/dyn/technologies/migrate-your-services/)) | [octodns_dyn](https://github.com/octodns/octodns-dyn/) | | -| [easyDNS](https://easydns.com/) | [octodns_easydns](https://github.com/octodns/octodns-easydns/) | | -| [EdgeCenter DNS](https://edgecenter.ru/dns/) | [octodns_edgecenter](https://github.com/octodns/octodns-edgecenter/) | | -| /etc/hosts | [octodns_etchosts](https://github.com/octodns/octodns-etchosts/) | | -| [Gandi](https://www.gandi.net/en-US/domain/dns) | [octodns_gandi](https://github.com/octodns/octodns-gandi/) | | -| [G-Core Labs DNS](https://gcorelabs.com/dns/) | [octodns_gcore](https://github.com/octodns/octodns-gcore/) | | -| [Google Cloud DNS](https://cloud.google.com/dns) | [octodns_googlecloud](https://github.com/octodns/octodns-googlecloud/) | | -| [Hetzner DNS](https://www.hetzner.com/dns-console) | [octodns_hetzner](https://github.com/octodns/octodns-hetzner/) | | -| [Mythic Beasts DNS](https://www.mythic-beasts.com/support/hosting/dns) | [octodns_mythicbeasts](https://github.com/octodns/octodns-mythicbeasts/) | | -| [NS1](https://ns1.com/products/managed-dns) | [octodns_ns1](https://github.com/octodns/octodns-ns1/) | | -| [OVHcloud DNS](https://www.ovhcloud.com/en/domains/dns-subdomain/) | [octodns_ovh](https://github.com/octodns/octodns-ovh/) | | -| [PowerDNS](https://www.powerdns.com/) | [octodns_powerdns](https://github.com/octodns/octodns-powerdns/) | | -| [Rackspace](https://www.rackspace.com/library/what-is-dns) | [octodns_rackspace](https://github.com/octodns/octodns-rackspace/) | | -| [Scaleway](https://www.scaleway.com/en/dns/) | [octodns_scaleway](https://github.com/scaleway/octodns-scaleway) | | -| [Selectel](https://selectel.ru/en/services/additional/dns/) | [octodns_selectel](https://github.com/octodns/octodns-selectel/) | | -| [SPF Value Management](https://github.com/octodns/octodns-spf) | [octodns_spf](https://github.com/octodns/octodns-spf/) | | -| [TransIP](https://www.transip.eu/knowledgebase/entry/155-dns-and-nameservers/) | [octodns_transip](https://github.com/octodns/octodns-transip/) | | -| [UltraDNS](https://vercara.com/authoritative-dns) | [octodns_ultra](https://github.com/octodns/octodns-ultra/) | | -| [YamlProvider](/octodns/provider/yaml.py) | built-in | Supports all record types and core functionality | -| [deSEC](https://desec.io/) | [octodns_desec](https://github.com/rootshell-labs/octodns-desec) | | -| [ClouDNS](https://www.cloudns.net/) | [octodns_cloudns](https://github.com/ClouDNS/octodns_cloudns) | | - +| Provider | Module | Notes | +| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| /etc/hosts | [octodns_etchosts](https://github.com/octodns/octodns-etchosts/) | | +| [Akamai Edge DNS](https://www.akamai.com/products/edge-dns) | [octodns_edgedns](https://github.com/octodns/octodns-edgedns/) | | +| [Amazon Route 53](https://aws.amazon.com/route53/) | [octodns_route53](https://github.com/octodns/octodns-route53) | | +| [AutoDNS](https://www.internetx.com/autodns/) | [octodns_autodns](https://github.com/octodns/octodns-autodns) | | +| [Azure DNS](https://azure.microsoft.com/en-us/services/dns/) | [octodns_azure](https://github.com/octodns/octodns-azure/) | | +| [BIND, AXFR, RFC-2136](https://www.isc.org/bind/) | [octodns_bind](https://github.com/octodns/octodns-bind/) | | +| [Cloudflare DNS](https://www.cloudflare.com/dns/) | [octodns_cloudflare](https://github.com/octodns/octodns-cloudflare/) | | +| [ClouDNS](https://www.cloudns.net/) | [octodns_cloudns](https://github.com/ClouDNS/octodns_cloudns) | | +| [Constellix](https://constellix.com/) | [octodns_constellix](https://github.com/octodns/octodns-constellix/) | | +| [deSEC](https://desec.io/) | [octodns_desec](https://github.com/rootshell-labs/octodns-desec) | | +| [DigitalOcean](https://docs.digitalocean.com/products/networking/dns/) | [octodns_digitalocean](https://github.com/octodns/octodns-digitalocean/) | | +| [DNS Made Easy](https://dnsmadeeasy.com/) | [octodns_dnsmadeeasy](https://github.com/octodns/octodns-dnsmadeeasy/) | | +| [DNSimple](https://dnsimple.com/) | [octodns_dnsimple](https://github.com/octodns/octodns-dnsimple/) | | +| [Dyn](https://www.oracle.com/cloud/networking/dns/) ([deprecated](https://www.oracle.com/corporate/acquisitions/dyn/technologies/migrate-your-services/)) | [octodns_dyn](https://github.com/octodns/octodns-dyn/) | | +| [easyDNS](https://easydns.com/) | [octodns_easydns](https://github.com/octodns/octodns-easydns/) | | +| [EdgeCenter DNS](https://edgecenter.ru/dns/) | [octodns_edgecenter](https://github.com/octodns/octodns-edgecenter/) | | +| [Fastly](https://www.fastly.com/de/) | [Financial-Times/octodns-fastly](https://github.com/Financial-Times/octodns-fastly) | | +| [G-Core Labs DNS](https://gcorelabs.com/dns/) | [octodns_gcore](https://github.com/octodns/octodns-gcore/) | | +| [Gandi](https://www.gandi.net/en-US/domain/dns) | [octodns_gandi](https://github.com/octodns/octodns-gandi/) | | +| [Google Cloud DNS](https://cloud.google.com/dns) | [octodns_googlecloud](https://github.com/octodns/octodns-googlecloud/) | | +| [Hetzner DNS](https://www.hetzner.com/dns-console) | [octodns_hetzner](https://github.com/octodns/octodns-hetzner/) | | +| [Infoblox](https://www.infoblox.com/) | [asyncon/octoblox](https://github.com/asyncon/octoblox) | | +| [Infomaniak](https://www.infomaniak.com/) | [octodns_infomaniak](https://github.com/M0NsTeRRR/octodns-infomaniak) | | +| [Lexicon](https://dns-lexicon.github.io/dns-lexicon/#) | [dns-lexicon/dns-lexicon](https://github.com/dns-lexicon/dns-lexicon) | | +| [Mythic Beasts DNS](https://www.mythic-beasts.com/support/hosting/dns) | [octodns_mythicbeasts](https://github.com/octodns/octodns-mythicbeasts/) | | +| [NetBox-DNS Plugin](https://github.com/peteeckel/netbox-plugin-dns) | [olofvndrhr/octodns-netbox-dns](https://github.com/olofvndrhr/octodns-netbox-dns) | | +| [NS1](https://ns1.com/products/managed-dns) | [octodns_ns1](https://github.com/octodns/octodns-ns1/) | | +| [OVHcloud DNS](https://www.ovhcloud.com/en/domains/dns-subdomain/) | [octodns_ovh](https://github.com/octodns/octodns-ovh/) | | +| [Pi-hole](https://pi-hole.net/) | [jvoss/octodns-pihole](https://github.com/jvoss/octodns-pihole) | | +| [PowerDNS](https://www.powerdns.com/) | [octodns_powerdns](https://github.com/octodns/octodns-powerdns/) | | +| [Rackspace](https://www.rackspace.com/library/what-is-dns) | [octodns_rackspace](https://github.com/octodns/octodns-rackspace/) | | +| [Scaleway](https://www.scaleway.com/en/dns/) | [octodns_scaleway](https://github.com/scaleway/octodns-scaleway) | | +| [Selectel](https://selectel.ru/en/services/additional/dns/) | [octodns_selectel](https://github.com/octodns/octodns-selectel/) | | +| [SPF Value Management](https://github.com/octodns/octodns-spf) | [octodns_spf](https://github.com/octodns/octodns-spf/) | | +| [TransIP](https://www.transip.eu/knowledgebase/entry/155-dns-and-nameservers/) | [octodns_transip](https://github.com/octodns/octodns-transip/) | | +| [UltraDNS](https://vercara.com/authoritative-dns) | [octodns_ultra](https://github.com/octodns/octodns-ultra/) | | +| [YamlProvider](/octodns/provider/yaml.py) | built-in | Supports all record types and core functionality | +| Zonefile | [kompetenzbolzen/octodns-custom-provider](https://github.com/kompetenzbolzen/octodns-custom-provider) | | ### Updating to use extracted providers @@ -314,40 +319,44 @@ The module required and provider class path for extracted providers can be found Similar to providers, but can only serve to populate records into a zone, cannot be synced to. -| Source | Record Support | Dynamic | Notes | -|--|--|--|--| -| [EnvVarSource](/octodns/source/envvar.py) | TXT | No | read-only environment variable injection | -| [AxfrSource](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, LOC, MX, NS, PTR, SPF, SRV, TXT | No | read-only | -| [ZoneFileSource](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | read-only | -| [TinyDnsFileSource](/octodns/source/tinydns.py) | A, CNAME, MX, NS, PTR | No | read-only | +| Source | Record Support | Dynamic | Notes | +| ---------------------------------------------------------------------------- | ---------------------------------------------------- | ------- | ---------------------------------------- | +| [AxfrSource (BIND)](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, LOC, MX, NS, PTR, SPF, SRV, TXT | No | read-only | +| [DDNS Source](https://github.com/octodns/octodns-ddns) | A, AAAA | No | read-only | +| [EnvVarSource](/octodns/source/envvar.py) | TXT | No | read-only environment variable injection | +| [Lexicon Source](https://github.com/doddo/octodns-lexicon) | A, 'AAA, ALIAS, CAA, CNAME, MX, NS, SRV, TXT | No | read-only | +| [Netbox Source](https://github.com/sukiyaki/octodns-netbox) | A, AAAA, PTR | No | read-only | +| [PHPIPAM source](https://github.com/kompetenzbolzen/octodns-custom-provider) | A, AAAA | No | read-only | +| [TinyDnsFileSource](/octodns/source/tinydns.py) | A, CNAME, MX, NS, PTR | No | read-only | +| [ZoneFileSource](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | read-only | ### Notes -* ALIAS support varies a lot from provider to provider care should be taken to verify that your needs are met in detail. - * Dyn's UI doesn't allow editing or view of TTL, but the API accepts and stores the value provided, this value does not appear to be used when served - * Dnsimple's uses the configured TTL when serving things through the ALIAS, there's also a secondary TXT record created alongside the ALIAS that octoDNS ignores -* octoDNS itself supports non-ASCII character sets, but in testing Cloudflare is the only provider where that is currently functional end-to-end. Others have failures either in the client libraries or API calls +- ALIAS support varies a lot from provider to provider care should be taken to verify that your needs are met in detail. +- Dyn's UI doesn't allow editing or view of TTL, but the API accepts and stores the value provided, this value does not appear to be used when served +- Dnsimple's uses the configured TTL when serving things through the ALIAS, there's also a secondary TXT record created alongside the ALIAS that octoDNS ignores +- octoDNS itself supports non-ASCII character sets, but in testing Cloudflare is the only provider where that is currently functional end-to-end. Others have failures either in the client libraries or API calls ## Processors -| Processor | Description | -|--|--| -| [AcmeManagingProcessor](/octodns/processor/acme.py) | Useful when processes external to octoDNS are managing acme challenge DNS records, e.g. LetsEncrypt | -| [AutoArpa](/octodns/processor/arpa.py) | See [Automatic PTR generation](#automatic-ptr-generation) below | -| [EnsureTrailingDots](/octodns/processor/trailing_dots.py) | Processor that ensures ALIAS, CNAME, DNAME, MX, NS, PTR, and SRVs have trailing dots | -| [ExcludeRootNsChanges](/octodns/processor/filter.py) | Filter that errors or warns on planned root/APEX NS records changes. | -| [IgnoreRootNsFilter](/octodns/processor/filter.py) | Filter that IGNORES root/APEX NS records and prevents octoDNS from trying to manage them (where supported.) | -| [MetaProcessor](/octodns/processor/meta.py) | Adds a special meta record with timing, UUID, providers, and/or version to aid in debugging and monitoring. | -| [NameAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified naming patterns, all others will be ignored | -| [NameRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified naming patterns, all others will be managed | -| [ValueAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified value patterns based on `rdata_text`, all others will be ignored | -| [ValueRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified value patterns based on `rdata_text`, all others will be managed | -| [OwnershipProcessor](/octodns/processor/ownership.py) | Processor that implements ownership in octoDNS so that it can manage only the records in a zone in sources and will ignore all others. | -| [SpfDnsLookupProcessor](/octodns/processor/spf.py) | Processor that checks SPF values for violations of DNS query limits | -| [TtlRestrictionFilter](/octodns/processor/restrict.py) | Processor that restricts the allow TTL values to a specified range or list of specific values | -| [TypeAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records of specified types, all others will be ignored | -| [TypeRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records of specified types, all others will be managed | -| [octodns-spf](https://github.com/octodns/octodns-spf) | SPF Value Management for octoDNS | +| Processor | Description | +| --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| [AcmeManagingProcessor](/octodns/processor/acme.py) | Useful when processes external to octoDNS are managing acme challenge DNS records, e.g. LetsEncrypt | +| [AutoArpa](/octodns/processor/arpa.py) | See [Automatic PTR generation](#automatic-ptr-generation) below | +| [EnsureTrailingDots](/octodns/processor/trailing_dots.py) | Processor that ensures ALIAS, CNAME, DNAME, MX, NS, PTR, and SRVs have trailing dots | +| [ExcludeRootNsChanges](/octodns/processor/filter.py) | Filter that errors or warns on planned root/APEX NS records changes. | +| [IgnoreRootNsFilter](/octodns/processor/filter.py) | Filter that IGNORES root/APEX NS records and prevents octoDNS from trying to manage them (where supported.) | +| [MetaProcessor](/octodns/processor/meta.py) | Adds a special meta record with timing, UUID, providers, and/or version to aid in debugging and monitoring. | +| [NameAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified naming patterns, all others will be ignored | +| [NameRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified naming patterns, all others will be managed | +| [ValueAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified value patterns based on `rdata_text`, all others will be ignored | +| [ValueRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified value patterns based on `rdata_text`, all others will be managed | +| [OwnershipProcessor](/octodns/processor/ownership.py) | Processor that implements ownership in octoDNS so that it can manage only the records in a zone in sources and will ignore all others. | +| [SpfDnsLookupProcessor](/octodns/processor/spf.py) | Processor that checks SPF values for violations of DNS query limits | +| [TtlRestrictionFilter](/octodns/processor/restrict.py) | Processor that restricts the allow TTL values to a specified range or list of specific values | +| [TypeAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records of specified types, all others will be ignored | +| [TypeRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records of specified types, all others will be managed | +| [octodns-spf](https://github.com/octodns/octodns-spf) | SPF Value Management for octoDNS | ## Automatic PTR generation @@ -381,7 +390,7 @@ providers: ## Custom Sources and Providers -You can check out the [source](/octodns/source/) and [provider](/octodns/provider/) directory to see what's currently supported. Sources act as a source of record information. AxfrSource and TinyDnsFileSource are currently the only OSS sources, though we have several others internally that are specific to our environment. These include something to pull host data from [gPanel](https://githubengineering.com/githubs-metal-cloud/) and a similar provider that sources information about our network gear to create both `A` & `PTR` records for their interfaces. Things that might make good OSS sources might include an `ElbSource` that pulls information about [AWS Elastic Load Balancers](https://aws.amazon.com/elasticloadbalancing/) and dynamically creates `CNAME`s for them, or `Ec2Source` that pulls instance information so that records can be created for hosts similar to how our `GPanelProvider` works. +You can check out the [source](/octodns/source/) and [provider](/octodns/provider/) directory to see what's currently supported. Sources act as a source of record information. AxfrSource and TinyDnsFileSource are currently the only OSS sources, though we have several others internally that are specific to our environment. These include something to pull host data from [gPanel](https://githubengineering.com/githubs-metal-cloud/) and a similar provider that sources information about our network gear to create both `A` & `PTR` records for their interfaces. Things that might make good OSS sources might include an `ElbSource` that pulls information about [AWS Elastic Load Balancers](https://aws.amazon.com/elasticloadbalancing/) and dynamically creates `CNAME`s for them, or `Ec2Source` that pulls instance information so that records can be created for hosts similar to how our `GPanelProvider` works. Most of the things included in octoDNS are providers, the obvious difference being that they can serve as both sources and targets of data. We'd really like to see this list grow over time so if you use an unsupported provider then PRs are welcome. The existing providers should serve as reasonable examples. Those that have no GeoDNS support are relatively straightforward. Unfortunately most of the APIs involved to do GeoDNS style traffic management are complex and somewhat inconsistent so adding support for that function would be nice, but is optional and best done in a separate pass. @@ -409,7 +418,6 @@ providers: password: env/DYN_PASSWORD zones: - githubtest.net.: sources: - route53 @@ -426,15 +434,14 @@ Internally we use custom sources to create records based on dynamic data that ch providers: gpanel-site: class: github.octodns.source.gpanel.GPanelProvider - host: 'gpanel.site.github.foo' + host: "gpanel.site.github.foo" token: env/GPANEL_SITE_TOKEN powerdns-site: class: octodns.provider.powerdns.PowerDnsProvider - host: 'internal-dns.site.github.foo' + host: "internal-dns.site.github.foo" api_key: env/POWERDNS_SITE_API_KEY zones: - hosts.site.github.foo.: sources: - gpanel-site @@ -455,20 +462,12 @@ If you have a problem or suggestion, please [open an issue](https://github.com/o - **GitHub Action:** [octoDNS-Sync](https://github.com/marketplace/actions/octodns-sync) - **NixOS Integration:** [NixOS-DNS](https://github.com/Janik-Haag/nixos-dns/) - **Sample Implementations.** See how others are using it + - [`hackclub/dns`](https://github.com/hackclub/dns) - [`kubernetes/k8s.io:/dns`](https://github.com/kubernetes/k8s.io/tree/main/dns) - [`g0v-network/domains`](https://github.com/g0v-network/domains) - [`jekyll/dns`](https://github.com/jekyll/dns) -- **Custom Sources & Providers.** - - [`octodns/octodns-ddns`](https://github.com/octodns/octodns-ddns): A simple Dynamic DNS source. - - [`doddo/octodns-lexicon`](https://github.com/doddo/octodns-lexicon): Use [Lexicon](https://github.com/AnalogJ/lexicon) providers as octoDNS providers. - - [`asyncon/octoblox`](https://github.com/asyncon/octoblox): [Infoblox](https://www.infoblox.com/) provider. - - [`sukiyaki/octodns-netbox`](https://github.com/sukiyaki/octodns-netbox): [NetBox](https://github.com/netbox-community/netbox) source. - - [`olofvndrhr/octodns-netbox-dns`](https://github.com/olofvndrhr/octodns-netbox-dns): [NetBox-DNS Plugin](https://github.com/peteeckel/netbox-plugin-dns) provider. - - [`kompetenzbolzen/octodns-custom-provider`](https://github.com/kompetenzbolzen/octodns-custom-provider): zonefile provider & phpIPAM source. - - [`Financial-Times/octodns-fastly`](https://github.com/Financial-Times/octodns-fastly): An octoDNS source for Fastly. - - [`jvoss/octodns-pihole`](https://github.com/jvoss/octodns-pihole): [Pi-hole](https://pi-hole.net/) provider. - - [`M0NsTeRRR/octodns-infomaniak`](https://github.com/M0NsTeRRR/octodns-infomaniak): [Infomaniak](https://www.infomaniak.com/) provider. + - **Resources.** - Article: [Visualising DNS records with Neo4j](https://medium.com/@costask/querying-and-visualising-octodns-records-with-neo4j-f4f72ab2d474) + code - Video: [FOSDEM 2019 - DNS as code with octodns](https://archive.fosdem.org/2019/schedule/event/dns_octodns/) @@ -482,10 +481,10 @@ If you know of any other resources, please do let us know! octoDNS is licensed under the [MIT license](LICENSE). -The MIT license grant is not for GitHub's trademarks, which include the logo designs. GitHub reserves all trademark and copyright rights in and to all GitHub trademarks. GitHub's logos include, for instance, the stylized designs that include "logo" in the file title in the following folder: https://github.com/octodns/octodns/tree/main/docs/logos/ +The MIT license grant is not for GitHub's trademarks, which include the logo designs. GitHub reserves all trademark and copyright rights in and to all GitHub trademarks. GitHub's logos include, for instance, the stylized designs that include "logo" in the file title in the following folder: GitHub® and its stylized versions and the Invertocat mark are GitHub's Trademarks or registered Trademarks. When using GitHub's logos, be sure to follow the GitHub logo guidelines. ## Authors -octoDNS was designed and authored by [Ross McFarland](https://github.com/ross) and [Joe Williams](https://github.com/joewilliams). See https://github.com/octodns/octodns/graphs/contributors for a complete list of people who've contributed. +octoDNS was designed and authored by [Ross McFarland](https://github.com/ross) and [Joe Williams](https://github.com/joewilliams). See for a complete list of people who've contributed. From cef9ad48551faa0fc375ea73572d11916a38959d Mon Sep 17 00:00:00 2001 From: Ivan Schaller Date: Sat, 14 Jun 2025 18:38:48 +0200 Subject: [PATCH 09/29] fix markdown format Signed-off-by: Ivan Schaller --- .../acae8de42da4482ea06e380586010eb8.md | 4 + README.md | 214 +++++++++--------- 2 files changed, 111 insertions(+), 107 deletions(-) create mode 100644 .changelog/acae8de42da4482ea06e380586010eb8.md diff --git a/.changelog/acae8de42da4482ea06e380586010eb8.md b/.changelog/acae8de42da4482ea06e380586010eb8.md new file mode 100644 index 0000000..8c7aeb5 --- /dev/null +++ b/.changelog/acae8de42da4482ea06e380586010eb8.md @@ -0,0 +1,4 @@ +--- +type: none +--- +fix markdown format \ No newline at end of file diff --git a/README.md b/README.md index c38c256..c0e8100 100644 --- a/README.md +++ b/README.md @@ -9,37 +9,37 @@ The architecture is pluggable and the tooling is flexible to make it applicable ## Table of Contents -- [Getting started](#getting-started) - - [Workspace](#workspace) - - [Installing a specific commit SHA](#installing-a-specific-commit-sha) - - [Config](#config) - - [Dynamic Zone Config](#dynamic-zone-config) - - [Static Zone Config](#static-zone-config) - - [General Configuration Concepts](#general-configuration-concepts) - - [Quick Example Record](#quick-example-record) - - [Noop](#noop) - - [Making changes](#making-changes) - - [Workflow](#workflow) - - [Bootstrapping config files](#bootstrapping-config-files) -- [Providers](#providers) - - [Updating to use extracted providers](#updating-to-use-extracted-providers) -- [Sources](#sources) - - [Notes](#notes) -- [Processors](#processors) -- [Automatic PTR generation](#automatic-ptr-generation) -- [Compatibility and Compliance](#compatibility-and-compliance) - - [`lenient`](#lenient) - - [`strict_supports`](#strict_supports) - - [Configuring `strict_supports`](#configuring-strict_supports) -- [Custom Sources and Providers](#custom-sources-and-providers) -- [Other Uses](#other-uses) - - [Syncing between providers](#syncing-between-providers) - - [Dynamic sources](#dynamic-sources) -- [Contributing](#contributing) -- [Getting help](#getting-help) -- [Related Projects and Resources](#related-projects-and-resources) -- [License](#license) -- [Authors](#authors) +* [Getting started](#getting-started) + * [Workspace](#workspace) + * [Installing a specific commit SHA](#installing-a-specific-commit-sha) + * [Config](#config) + * [Dynamic Zone Config](#dynamic-zone-config) + * [Static Zone Config](#static-zone-config) + * [General Configuration Concepts](#general-configuration-concepts) + * [Quick Example Record](#quick-example-record) + * [Noop](#noop) + * [Making changes](#making-changes) + * [Workflow](#workflow) + * [Bootstrapping config files](#bootstrapping-config-files) +* [Providers](#providers) + * [Updating to use extracted providers](#updating-to-use-extracted-providers) +* [Sources](#sources) + * [Notes](#notes) +* [Processors](#processors) +* [Automatic PTR generation](#automatic-ptr-generation) +* [Compatibility and Compliance](#compatibility-and-compliance) + * [`lenient`](#lenient) + * [`strict_supports`](#strict_supports) + * [Configuring `strict_supports`](#configuring-strict_supports) +* [Custom Sources and Providers](#custom-sources-and-providers) +* [Other Uses](#other-uses) + * [Syncing between providers](#syncing-between-providers) + * [Dynamic sources](#dynamic-sources) +* [Contributing](#contributing) +* [Getting help](#getting-help) +* [Related Projects and Resources](#related-projects-and-resources) +* [License](#license) +* [Authors](#authors) ## Getting started @@ -63,7 +63,7 @@ $ mkdir config If you'd like to install a version that has not yet been released in a repeatable/safe manner you can do the following. In general octoDNS is fairly stable in between releases thanks to the plan and apply process, but care should be taken regardless. ```console -pip install -e git+https://git@github.com/octodns/octodns.git@#egg=octodns +$ pip install -e git+https://git@github.com/octodns/octodns.git@#egg=octodns ``` ### Config @@ -96,7 +96,7 @@ zones: # This is a dynamic zone config. The source(s), here `config`, will be # queried for a list of zone names and each will dynamically be set up to # match the dynamic entry. - "*": + '*': sources: - config targets: @@ -173,7 +173,7 @@ Now that we have something to tell octoDNS about our providers & zones we need t ```yaml --- -"": +'': ttl: 60 type: A values: @@ -267,46 +267,46 @@ It is important to review any `WARNING` log lines printed out during an `octodns The table below lists the providers octoDNS supports. They are maintained in their own repositories and released as independent modules. -| Provider | Module | Notes | +| Provider | Module | Notes | | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| /etc/hosts | [octodns_etchosts](https://github.com/octodns/octodns-etchosts/) | | -| [Akamai Edge DNS](https://www.akamai.com/products/edge-dns) | [octodns_edgedns](https://github.com/octodns/octodns-edgedns/) | | -| [Amazon Route 53](https://aws.amazon.com/route53/) | [octodns_route53](https://github.com/octodns/octodns-route53) | | -| [AutoDNS](https://www.internetx.com/autodns/) | [octodns_autodns](https://github.com/octodns/octodns-autodns) | | -| [Azure DNS](https://azure.microsoft.com/en-us/services/dns/) | [octodns_azure](https://github.com/octodns/octodns-azure/) | | -| [BIND, AXFR, RFC-2136](https://www.isc.org/bind/) | [octodns_bind](https://github.com/octodns/octodns-bind/) | | -| [Cloudflare DNS](https://www.cloudflare.com/dns/) | [octodns_cloudflare](https://github.com/octodns/octodns-cloudflare/) | | -| [ClouDNS](https://www.cloudns.net/) | [octodns_cloudns](https://github.com/ClouDNS/octodns_cloudns) | | -| [Constellix](https://constellix.com/) | [octodns_constellix](https://github.com/octodns/octodns-constellix/) | | -| [deSEC](https://desec.io/) | [octodns_desec](https://github.com/rootshell-labs/octodns-desec) | | -| [DigitalOcean](https://docs.digitalocean.com/products/networking/dns/) | [octodns_digitalocean](https://github.com/octodns/octodns-digitalocean/) | | -| [DNS Made Easy](https://dnsmadeeasy.com/) | [octodns_dnsmadeeasy](https://github.com/octodns/octodns-dnsmadeeasy/) | | -| [DNSimple](https://dnsimple.com/) | [octodns_dnsimple](https://github.com/octodns/octodns-dnsimple/) | | -| [Dyn](https://www.oracle.com/cloud/networking/dns/) ([deprecated](https://www.oracle.com/corporate/acquisitions/dyn/technologies/migrate-your-services/)) | [octodns_dyn](https://github.com/octodns/octodns-dyn/) | | -| [easyDNS](https://easydns.com/) | [octodns_easydns](https://github.com/octodns/octodns-easydns/) | | -| [EdgeCenter DNS](https://edgecenter.ru/dns/) | [octodns_edgecenter](https://github.com/octodns/octodns-edgecenter/) | | -| [Fastly](https://www.fastly.com/de/) | [Financial-Times/octodns-fastly](https://github.com/Financial-Times/octodns-fastly) | | -| [G-Core Labs DNS](https://gcorelabs.com/dns/) | [octodns_gcore](https://github.com/octodns/octodns-gcore/) | | -| [Gandi](https://www.gandi.net/en-US/domain/dns) | [octodns_gandi](https://github.com/octodns/octodns-gandi/) | | -| [Google Cloud DNS](https://cloud.google.com/dns) | [octodns_googlecloud](https://github.com/octodns/octodns-googlecloud/) | | -| [Hetzner DNS](https://www.hetzner.com/dns-console) | [octodns_hetzner](https://github.com/octodns/octodns-hetzner/) | | -| [Infoblox](https://www.infoblox.com/) | [asyncon/octoblox](https://github.com/asyncon/octoblox) | | -| [Infomaniak](https://www.infomaniak.com/) | [octodns_infomaniak](https://github.com/M0NsTeRRR/octodns-infomaniak) | | -| [Lexicon](https://dns-lexicon.github.io/dns-lexicon/#) | [dns-lexicon/dns-lexicon](https://github.com/dns-lexicon/dns-lexicon) | | -| [Mythic Beasts DNS](https://www.mythic-beasts.com/support/hosting/dns) | [octodns_mythicbeasts](https://github.com/octodns/octodns-mythicbeasts/) | | -| [NetBox-DNS Plugin](https://github.com/peteeckel/netbox-plugin-dns) | [olofvndrhr/octodns-netbox-dns](https://github.com/olofvndrhr/octodns-netbox-dns) | | -| [NS1](https://ns1.com/products/managed-dns) | [octodns_ns1](https://github.com/octodns/octodns-ns1/) | | -| [OVHcloud DNS](https://www.ovhcloud.com/en/domains/dns-subdomain/) | [octodns_ovh](https://github.com/octodns/octodns-ovh/) | | -| [Pi-hole](https://pi-hole.net/) | [jvoss/octodns-pihole](https://github.com/jvoss/octodns-pihole) | | -| [PowerDNS](https://www.powerdns.com/) | [octodns_powerdns](https://github.com/octodns/octodns-powerdns/) | | -| [Rackspace](https://www.rackspace.com/library/what-is-dns) | [octodns_rackspace](https://github.com/octodns/octodns-rackspace/) | | -| [Scaleway](https://www.scaleway.com/en/dns/) | [octodns_scaleway](https://github.com/scaleway/octodns-scaleway) | | -| [Selectel](https://selectel.ru/en/services/additional/dns/) | [octodns_selectel](https://github.com/octodns/octodns-selectel/) | | -| [SPF Value Management](https://github.com/octodns/octodns-spf) | [octodns_spf](https://github.com/octodns/octodns-spf/) | | -| [TransIP](https://www.transip.eu/knowledgebase/entry/155-dns-and-nameservers/) | [octodns_transip](https://github.com/octodns/octodns-transip/) | | -| [UltraDNS](https://vercara.com/authoritative-dns) | [octodns_ultra](https://github.com/octodns/octodns-ultra/) | | -| [YamlProvider](/octodns/provider/yaml.py) | built-in | Supports all record types and core functionality | -| Zonefile | [kompetenzbolzen/octodns-custom-provider](https://github.com/kompetenzbolzen/octodns-custom-provider) | | +| /etc/hosts | [octodns_etchosts](https://github.com/octodns/octodns-etchosts/) | | +| [Akamai Edge DNS](https://www.akamai.com/products/edge-dns) | [octodns_edgedns](https://github.com/octodns/octodns-edgedns/) | | +| [Amazon Route 53](https://aws.amazon.com/route53/) | [octodns_route53](https://github.com/octodns/octodns-route53) | | +| [AutoDNS](https://www.internetx.com/autodns/) | [octodns_autodns](https://github.com/octodns/octodns-autodns) | | +| [Azure DNS](https://azure.microsoft.com/en-us/services/dns/) | [octodns_azure](https://github.com/octodns/octodns-azure/) | | +| [BIND, AXFR, RFC-2136](https://www.isc.org/bind/) | [octodns_bind](https://github.com/octodns/octodns-bind/) | | +| [Cloudflare DNS](https://www.cloudflare.com/dns/) | [octodns_cloudflare](https://github.com/octodns/octodns-cloudflare/) | | +| [ClouDNS](https://www.cloudns.net/) | [octodns_cloudns](https://github.com/ClouDNS/octodns_cloudns) | | +| [Constellix](https://constellix.com/) | [octodns_constellix](https://github.com/octodns/octodns-constellix/) | | +| [deSEC](https://desec.io/) | [octodns_desec](https://github.com/rootshell-labs/octodns-desec) | | +| [DigitalOcean](https://docs.digitalocean.com/products/networking/dns/) | [octodns_digitalocean](https://github.com/octodns/octodns-digitalocean/) | | +| [DNS Made Easy](https://dnsmadeeasy.com/) | [octodns_dnsmadeeasy](https://github.com/octodns/octodns-dnsmadeeasy/) | | +| [DNSimple](https://dnsimple.com/) | [octodns_dnsimple](https://github.com/octodns/octodns-dnsimple/) | | +| [Dyn](https://www.oracle.com/cloud/networking/dns/) ([deprecated](https://www.oracle.com/corporate/acquisitions/dyn/technologies/migrate-your-services/)) | [octodns_dyn](https://github.com/octodns/octodns-dyn/) | | +| [easyDNS](https://easydns.com/) | [octodns_easydns](https://github.com/octodns/octodns-easydns/) | | +| [EdgeCenter DNS](https://edgecenter.ru/dns/) | [octodns_edgecenter](https://github.com/octodns/octodns-edgecenter/) | | +| [Fastly](https://www.fastly.com/de/) | [Financial-Times/octodns-fastly](https://github.com/Financial-Times/octodns-fastly) | | +| [G-Core Labs DNS](https://gcorelabs.com/dns/) | [octodns_gcore](https://github.com/octodns/octodns-gcore/) | | +| [Gandi](https://www.gandi.net/en-US/domain/dns) | [octodns_gandi](https://github.com/octodns/octodns-gandi/) | | +| [Google Cloud DNS](https://cloud.google.com/dns) | [octodns_googlecloud](https://github.com/octodns/octodns-googlecloud/) | | +| [Hetzner DNS](https://www.hetzner.com/dns-console) | [octodns_hetzner](https://github.com/octodns/octodns-hetzner/) | | +| [Infoblox](https://www.infoblox.com/) | [asyncon/octoblox](https://github.com/asyncon/octoblox) | | +| [Infomaniak](https://www.infomaniak.com/) | [octodns_infomaniak](https://github.com/M0NsTeRRR/octodns-infomaniak) | | +| [Lexicon](https://dns-lexicon.github.io/dns-lexicon/#) | [dns-lexicon/dns-lexicon](https://github.com/dns-lexicon/dns-lexicon) | | +| [Mythic Beasts DNS](https://www.mythic-beasts.com/support/hosting/dns) | [octodns_mythicbeasts](https://github.com/octodns/octodns-mythicbeasts/) | | +| [NetBox-DNS Plugin](https://github.com/peteeckel/netbox-plugin-dns) | [olofvndrhr/octodns-netbox-dns](https://github.com/olofvndrhr/octodns-netbox-dns) | | +| [NS1](https://ns1.com/products/managed-dns) | [octodns_ns1](https://github.com/octodns/octodns-ns1/) | | +| [OVHcloud DNS](https://www.ovhcloud.com/en/domains/dns-subdomain/) | [octodns_ovh](https://github.com/octodns/octodns-ovh/) | | +| [Pi-hole](https://pi-hole.net/) | [jvoss/octodns-pihole](https://github.com/jvoss/octodns-pihole) | | +| [PowerDNS](https://www.powerdns.com/) | [octodns_powerdns](https://github.com/octodns/octodns-powerdns/) | | +| [Rackspace](https://www.rackspace.com/library/what-is-dns) | [octodns_rackspace](https://github.com/octodns/octodns-rackspace/) | | +| [Scaleway](https://www.scaleway.com/en/dns/) | [octodns_scaleway](https://github.com/scaleway/octodns-scaleway) | | +| [Selectel](https://selectel.ru/en/services/additional/dns/) | [octodns_selectel](https://github.com/octodns/octodns-selectel/) | | +| [SPF Value Management](https://github.com/octodns/octodns-spf) | [octodns_spf](https://github.com/octodns/octodns-spf/) | | +| [TransIP](https://www.transip.eu/knowledgebase/entry/155-dns-and-nameservers/) | [octodns_transip](https://github.com/octodns/octodns-transip/) | | +| [UltraDNS](https://vercara.com/authoritative-dns) | [octodns_ultra](https://github.com/octodns/octodns-ultra/) | | +| [YamlProvider](/octodns/provider/yaml.py) | built-in | Supports all record types and core functionality | +| Zonefile | [kompetenzbolzen/octodns-custom-provider](https://github.com/kompetenzbolzen/octodns-custom-provider) | | ### Updating to use extracted providers @@ -319,44 +319,44 @@ The module required and provider class path for extracted providers can be found Similar to providers, but can only serve to populate records into a zone, cannot be synced to. -| Source | Record Support | Dynamic | Notes | +| Source | Record Support | Dynamic | Notes | | ---------------------------------------------------------------------------- | ---------------------------------------------------- | ------- | ---------------------------------------- | -| [AxfrSource (BIND)](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, LOC, MX, NS, PTR, SPF, SRV, TXT | No | read-only | -| [DDNS Source](https://github.com/octodns/octodns-ddns) | A, AAAA | No | read-only | -| [EnvVarSource](/octodns/source/envvar.py) | TXT | No | read-only environment variable injection | -| [Lexicon Source](https://github.com/doddo/octodns-lexicon) | A, 'AAA, ALIAS, CAA, CNAME, MX, NS, SRV, TXT | No | read-only | -| [Netbox Source](https://github.com/sukiyaki/octodns-netbox) | A, AAAA, PTR | No | read-only | -| [PHPIPAM source](https://github.com/kompetenzbolzen/octodns-custom-provider) | A, AAAA | No | read-only | -| [TinyDnsFileSource](/octodns/source/tinydns.py) | A, CNAME, MX, NS, PTR | No | read-only | -| [ZoneFileSource](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | read-only | +| [AxfrSource (BIND)](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, LOC, MX, NS, PTR, SPF, SRV, TXT | No | read-only | +| [DDNS Source](https://github.com/octodns/octodns-ddns) | A, AAAA | No | read-only | +| [EnvVarSource](/octodns/source/envvar.py) | TXT | No | read-only environment variable injection | +| [Lexicon Source](https://github.com/doddo/octodns-lexicon) | A, 'AAA, ALIAS, CAA, CNAME, MX, NS, SRV, TXT | No | read-only | +| [Netbox Source](https://github.com/sukiyaki/octodns-netbox) | A, AAAA, PTR | No | read-only | +| [PHPIPAM source](https://github.com/kompetenzbolzen/octodns-custom-provider) | A, AAAA | No | read-only | +| [TinyDnsFileSource](/octodns/source/tinydns.py) | A, CNAME, MX, NS, PTR | No | read-only | +| [ZoneFileSource](https://github.com/octodns/octodns-bind/) | A, AAAA, CAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | read-only | ### Notes -- ALIAS support varies a lot from provider to provider care should be taken to verify that your needs are met in detail. -- Dyn's UI doesn't allow editing or view of TTL, but the API accepts and stores the value provided, this value does not appear to be used when served -- Dnsimple's uses the configured TTL when serving things through the ALIAS, there's also a secondary TXT record created alongside the ALIAS that octoDNS ignores -- octoDNS itself supports non-ASCII character sets, but in testing Cloudflare is the only provider where that is currently functional end-to-end. Others have failures either in the client libraries or API calls +* ALIAS support varies a lot from provider to provider care should be taken to verify that your needs are met in detail. + * Dyn's UI doesn't allow editing or view of TTL, but the API accepts and stores the value provided, this value does not appear to be used when served + * Dnsimple's uses the configured TTL when serving things through the ALIAS, there's also a secondary TXT record created alongside the ALIAS that octoDNS ignores +* octoDNS itself supports non-ASCII character sets, but in testing Cloudflare is the only provider where that is currently functional end-to-end. Others have failures either in the client libraries or API calls ## Processors -| Processor | Description | +| Processor | Description | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| [AcmeManagingProcessor](/octodns/processor/acme.py) | Useful when processes external to octoDNS are managing acme challenge DNS records, e.g. LetsEncrypt | -| [AutoArpa](/octodns/processor/arpa.py) | See [Automatic PTR generation](#automatic-ptr-generation) below | -| [EnsureTrailingDots](/octodns/processor/trailing_dots.py) | Processor that ensures ALIAS, CNAME, DNAME, MX, NS, PTR, and SRVs have trailing dots | -| [ExcludeRootNsChanges](/octodns/processor/filter.py) | Filter that errors or warns on planned root/APEX NS records changes. | -| [IgnoreRootNsFilter](/octodns/processor/filter.py) | Filter that IGNORES root/APEX NS records and prevents octoDNS from trying to manage them (where supported.) | -| [MetaProcessor](/octodns/processor/meta.py) | Adds a special meta record with timing, UUID, providers, and/or version to aid in debugging and monitoring. | -| [NameAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified naming patterns, all others will be ignored | -| [NameRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified naming patterns, all others will be managed | -| [ValueAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified value patterns based on `rdata_text`, all others will be ignored | -| [ValueRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified value patterns based on `rdata_text`, all others will be managed | -| [OwnershipProcessor](/octodns/processor/ownership.py) | Processor that implements ownership in octoDNS so that it can manage only the records in a zone in sources and will ignore all others. | -| [SpfDnsLookupProcessor](/octodns/processor/spf.py) | Processor that checks SPF values for violations of DNS query limits | -| [TtlRestrictionFilter](/octodns/processor/restrict.py) | Processor that restricts the allow TTL values to a specified range or list of specific values | -| [TypeAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records of specified types, all others will be ignored | -| [TypeRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records of specified types, all others will be managed | -| [octodns-spf](https://github.com/octodns/octodns-spf) | SPF Value Management for octoDNS | +| [AcmeManagingProcessor](/octodns/processor/acme.py) | Useful when processes external to octoDNS are managing acme challenge DNS records, e.g. LetsEncrypt | +| [AutoArpa](/octodns/processor/arpa.py) | See [Automatic PTR generation](#automatic-ptr-generation) below | +| [EnsureTrailingDots](/octodns/processor/trailing_dots.py) | Processor that ensures ALIAS, CNAME, DNAME, MX, NS, PTR, and SRVs have trailing dots | +| [ExcludeRootNsChanges](/octodns/processor/filter.py) | Filter that errors or warns on planned root/APEX NS records changes. | +| [IgnoreRootNsFilter](/octodns/processor/filter.py) | Filter that IGNORES root/APEX NS records and prevents octoDNS from trying to manage them (where supported.) | +| [MetaProcessor](/octodns/processor/meta.py) | Adds a special meta record with timing, UUID, providers, and/or version to aid in debugging and monitoring. | +| [NameAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified naming patterns, all others will be ignored | +| [NameRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified naming patterns, all others will be managed | +| [ValueAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified value patterns based on `rdata_text`, all others will be ignored | +| [ValueRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records that match specified value patterns based on `rdata_text`, all others will be managed | +| [OwnershipProcessor](/octodns/processor/ownership.py) | Processor that implements ownership in octoDNS so that it can manage only the records in a zone in sources and will ignore all others. | +| [SpfDnsLookupProcessor](/octodns/processor/spf.py) | Processor that checks SPF values for violations of DNS query limits | +| [TtlRestrictionFilter](/octodns/processor/restrict.py) | Processor that restricts the allow TTL values to a specified range or list of specific values | +| [TypeAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records of specified types, all others will be ignored | +| [TypeRejectlistFilter](/octodns/processor/filter.py) | Filter that IGNORES records of specified types, all others will be managed | +| [octodns-spf](https://github.com/octodns/octodns-spf) | SPF Value Management for octoDNS | ## Automatic PTR generation @@ -390,7 +390,7 @@ providers: ## Custom Sources and Providers -You can check out the [source](/octodns/source/) and [provider](/octodns/provider/) directory to see what's currently supported. Sources act as a source of record information. AxfrSource and TinyDnsFileSource are currently the only OSS sources, though we have several others internally that are specific to our environment. These include something to pull host data from [gPanel](https://githubengineering.com/githubs-metal-cloud/) and a similar provider that sources information about our network gear to create both `A` & `PTR` records for their interfaces. Things that might make good OSS sources might include an `ElbSource` that pulls information about [AWS Elastic Load Balancers](https://aws.amazon.com/elasticloadbalancing/) and dynamically creates `CNAME`s for them, or `Ec2Source` that pulls instance information so that records can be created for hosts similar to how our `GPanelProvider` works. +You can check out the [source](/octodns/source/) and [provider](/octodns/provider/) directory to see what's currently supported. Sources act as a source of record information. AxfrSource and TinyDnsFileSource are currently the only OSS sources, though we have several others internally that are specific to our environment. These include something to pull host data from [gPanel](https://githubengineering.com/githubs-metal-cloud/) and a similar provider that sources information about our network gear to create both `A` & `PTR` records for their interfaces. Things that might make good OSS sources might include an `ElbSource` that pulls information about [AWS Elastic Load Balancers](https://aws.amazon.com/elasticloadbalancing/) and dynamically creates `CNAME`s for them, or `Ec2Source` that pulls instance information so that records can be created for hosts similar to how our `GPanelProvider` works. Most of the things included in octoDNS are providers, the obvious difference being that they can serve as both sources and targets of data. We'd really like to see this list grow over time so if you use an unsupported provider then PRs are welcome. The existing providers should serve as reasonable examples. Those that have no GeoDNS support are relatively straightforward. Unfortunately most of the APIs involved to do GeoDNS style traffic management are complex and somewhat inconsistent so adding support for that function would be nice, but is optional and best done in a separate pass. @@ -434,7 +434,7 @@ Internally we use custom sources to create records based on dynamic data that ch providers: gpanel-site: class: github.octodns.source.gpanel.GPanelProvider - host: "gpanel.site.github.foo" + host: 'gpanel.site.github.foo' token: env/GPANEL_SITE_TOKEN powerdns-site: class: octodns.provider.powerdns.PowerDnsProvider @@ -481,10 +481,10 @@ If you know of any other resources, please do let us know! octoDNS is licensed under the [MIT license](LICENSE). -The MIT license grant is not for GitHub's trademarks, which include the logo designs. GitHub reserves all trademark and copyright rights in and to all GitHub trademarks. GitHub's logos include, for instance, the stylized designs that include "logo" in the file title in the following folder: +The MIT license grant is not for GitHub's trademarks, which include the logo designs. GitHub reserves all trademark and copyright rights in and to all GitHub trademarks. GitHub's logos include, for instance, the stylized designs that include "logo" in the file title in the following folder: https://github.com/octodns/octodns/tree/main/docs/logos/ GitHub® and its stylized versions and the Invertocat mark are GitHub's Trademarks or registered Trademarks. When using GitHub's logos, be sure to follow the GitHub logo guidelines. ## Authors -octoDNS was designed and authored by [Ross McFarland](https://github.com/ross) and [Joe Williams](https://github.com/joewilliams). See for a complete list of people who've contributed. +octoDNS was designed and authored by [Ross McFarland](https://github.com/ross) and [Joe Williams](https://github.com/joewilliams). See https://github.com/octodns/octodns/graphs/contributors for a complete list of people who've contributed. From 4374dc01d7fa8a7b7335916f1e8243322f4ba41d Mon Sep 17 00:00:00 2001 From: Jonathan Leroy Date: Tue, 17 Jun 2025 03:10:29 +0200 Subject: [PATCH 10/29] New provider: Bunny DNS --- .changelog/0c0c814add3c4ce5983eb3a6d0430de4.md | 4 ++++ README.md | 1 + 2 files changed, 5 insertions(+) create mode 100644 .changelog/0c0c814add3c4ce5983eb3a6d0430de4.md diff --git a/.changelog/0c0c814add3c4ce5983eb3a6d0430de4.md b/.changelog/0c0c814add3c4ce5983eb3a6d0430de4.md new file mode 100644 index 0000000..24340ae --- /dev/null +++ b/.changelog/0c0c814add3c4ce5983eb3a6d0430de4.md @@ -0,0 +1,4 @@ +--- +type: minor +--- +New provider: Bunny DNS \ No newline at end of file diff --git a/README.md b/README.md index c0e8100..f561609 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ The table below lists the providers octoDNS supports. They are maintained in the | [AutoDNS](https://www.internetx.com/autodns/) | [octodns_autodns](https://github.com/octodns/octodns-autodns) | | | [Azure DNS](https://azure.microsoft.com/en-us/services/dns/) | [octodns_azure](https://github.com/octodns/octodns-azure/) | | | [BIND, AXFR, RFC-2136](https://www.isc.org/bind/) | [octodns_bind](https://github.com/octodns/octodns-bind/) | | +| [Bunny DNS](https://bunny.net/dns/) | [octodns_bunny](https://github.com/Relkian/octodns-bunny) | | | [Cloudflare DNS](https://www.cloudflare.com/dns/) | [octodns_cloudflare](https://github.com/octodns/octodns-cloudflare/) | | | [ClouDNS](https://www.cloudns.net/) | [octodns_cloudns](https://github.com/ClouDNS/octodns_cloudns) | | | [Constellix](https://constellix.com/) | [octodns_constellix](https://github.com/octodns/octodns-constellix/) | | From 31be2c5de58dd2c1300315b68c0e2d35e598fdf7 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 16 Jun 2025 19:48:17 -0700 Subject: [PATCH 11/29] Add context to Templating, cleanly handle records w/o template method, doc --- octodns/processor/templating.py | 64 +++++++++++- tests/test_octodns_processor_templating.py | 114 +++++++++++++++++++-- 2 files changed, 171 insertions(+), 7 deletions(-) diff --git a/octodns/processor/templating.py b/octodns/processor/templating.py index 144264c..c514bec 100644 --- a/octodns/processor/templating.py +++ b/octodns/processor/templating.py @@ -6,9 +6,58 @@ from octodns.processor.base import BaseProcessor class Templating(BaseProcessor): + ''' + Record templating using python format. For simple records like TXT and CAA + that is the value itself. For multi-field records like MX or SRV it's the + text portions, exchange and target respectively. - def __init__(self, id, *args, **kwargs): + Example Processor Config: + + templating: + class: octodns.processor.templating.Templating + # Any k/v present in context will be passed into the .format method and + # thus be available as additional variables in the template. This is all + # optional. + context: + key: value + another: 42 + + Example Records: + + foo: + type: TXT + value: The zone this record lives in is {zone_name}. There are {zone_num_records} record(s). + + bar: + type: MX + values: + - preference: 1 + exchange: mx1.{zone_name}.mail.mx. + - preference: 1 + exchange: mx2.{zone_name}.mail.mx. + + Note that validations for some types reject values with {}. When + encountered the best option is to use record level `lenient: true` + https://github.com/octodns/octodns/blob/main/docs/records.md#lenience + + Note that if you need to add dynamic context you can create a custom + processor that inherits from Templating and passes them into the call to + super, e.g. + + class MyTemplating(Templating): + def __init__(self, *args, context={}, **kwargs): + context['year'] = lambda desired, sources: datetime.now().strftime('%Y') + super().__init__(*args, context, **kwargs) + + See https://docs.python.org/3/library/string.html#custom-string-formatting + for details on formatting options. Anything possible in an `f-string` or + `.format` should work here. + + ''' + + def __init__(self, id, *args, context={}, **kwargs): super().__init__(id, *args, **kwargs) + self.context = context def process_source_zone(self, desired, sources): sources = sources or [] @@ -18,6 +67,13 @@ class Templating(BaseProcessor): 'zone_encoded_name': desired.name, 'zone_num_records': len(desired.records), 'zone_source_ids': ', '.join(s.id for s in sources), + # add any extra context provided to us, if the value is a callable + # object call it passing our params so that arbitrary dynamic + # context can be added for use in formatting + **{ + k: (v(desired, sources) if callable(v) else v) + for k, v in self.context.items() + }, } def params(record): @@ -36,12 +92,18 @@ class Templating(BaseProcessor): for record in desired.records: if hasattr(record, 'values'): + if record.values and not hasattr(record.values[0], 'template'): + # the (custom) value type does not support templating + continue new_values = [v.template(params(record)) for v in record.values] if record.values != new_values: new = record.copy() new.values = new_values desired.add_record(new, replace=True) else: + if not hasattr(record.value, 'template'): + # the (custom) value type does not support templating + continue new_value = record.value.template(params(record)) if record.value != new_value: new = record.copy() diff --git a/tests/test_octodns_processor_templating.py b/tests/test_octodns_processor_templating.py index 80a591a..94472d8 100644 --- a/tests/test_octodns_processor_templating.py +++ b/tests/test_octodns_processor_templating.py @@ -6,10 +6,59 @@ from unittest import TestCase from unittest.mock import call, patch from octodns.processor.templating import Templating -from octodns.record import Record +from octodns.record import Record, ValueMixin, ValuesMixin from octodns.zone import Zone +class DummySource: + + def __init__(self, id): + self.id = str(id) + + +class CustomValue(str): + + @classmethod + def validate(cls, *args, **kwargs): + return [] + + @classmethod + def process(cls, v): + if isinstance(v, (list, tuple)): + return (CustomValue(i) for i in v) + return CustomValue(v) + + @classmethod + def parse_rdata_text(cls, *args, **kwargs): + pass + + def __init__(self, *args, **kwargs): + self._asked_for = set() + + def rdata_text(self): + pass + + def __getattr__(self, item): + self._asked_for.add(item) + raise AttributeError('nope') + + +class Single(ValueMixin, Record): + _type = 'S' + _value_type = CustomValue + + +Record.register_type(Single, 'S') + + +class Multiple(ValuesMixin, Record): + _type = 'M' + _value_type = CustomValue + + +Record.register_type(Multiple, 'M') + + def _find(zone, name): return next(r for r in zone.records if r.name == name) @@ -75,14 +124,29 @@ class TemplatingTest(TestCase): noop = _find(got, 'noop') self.assertEqual('Nothing to template here.', noop.values[0]) - @patch('octodns.record.TxtValue.template') - def test_params(self, mock_template): + def test_no_template(self): templ = Templating('test') - class DummySource: + zone = Zone('unit.tests.', []) + s = Record.new(zone, 's', {'type': 'S', 'ttl': 42, 'value': 'string'}) + zone.add_record(s) - def __init__(self, id): - self.id = id + m = Record.new( + zone, 'm', {'type': 'M', 'ttl': 43, 'values': ('string', 'another')} + ) + zone.add_record(m) + + # this should check for the template method on our values that don't + # have one + templ.process_source_zone(zone, None) + # and these should make sure that the value types were asked if they + # have a template method + self.assertEqual({'template'}, s.value._asked_for) + self.assertEqual({'template'}, m.values[0]._asked_for) + + @patch('octodns.record.TxtValue.template') + def test_params(self, mock_template): + templ = Templating('test') zone = Zone('unit.tests.', []) record_source = DummySource('record') @@ -123,3 +187,41 @@ class TemplatingTest(TestCase): ), mock_template.call_args, ) + + def test_context(self): + templ = Templating( + 'test', + context={ + # static + 'the_answer': 42, + # dynamic + 'the_date': lambda _, __: 'today', + # uses a param + 'num_sources': lambda z, ss: len(ss), + }, + ) + + zone = Zone('unit.tests.', []) + txt = Record.new( + zone, + 'txt', + { + 'type': 'TXT', + 'ttl': 42, + 'values': ( + 'the_answer: {the_answer}', + 'the_date: {the_date}', + 'num_sources: {num_sources}', + ), + }, + ) + zone.add_record(txt) + + got = templ.process_source_zone( + zone, tuple(DummySource(i) for i in range(3)) + ) + txt = _find(got, 'txt') + self.assertEqual(3, len(txt.values)) + self.assertEqual('num_sources: 3', txt.values[0]) + self.assertEqual('the_answer: 42', txt.values[1]) + self.assertEqual('the_date: today', txt.values[2]) From 192ed51d7b3b366d4659e11e539e895ec9fec2db Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 16 Jun 2025 19:55:56 -0700 Subject: [PATCH 12/29] correct templating changelog msg --- .changelog/4af5a11fb21842ffb627d5ee4d80fb14.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md b/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md index 3b7ed8f..ece6385 100644 --- a/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md +++ b/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md @@ -1,4 +1,4 @@ --- type: minor --- -Add new Templating proccors \ No newline at end of file +Templating processor added From 8cb4566b7336f33fbfc06401c1bebc5fcf974417 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 08:42:07 -0700 Subject: [PATCH 13/29] =?UTF-8?q?Update=20geo-data,=20T=C3=BCrkiye?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changelog/c981d53176e142ba94d885a13e608faa.md | 4 ++++ octodns/record/geo_data.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changelog/c981d53176e142ba94d885a13e608faa.md diff --git a/.changelog/c981d53176e142ba94d885a13e608faa.md b/.changelog/c981d53176e142ba94d885a13e608faa.md new file mode 100644 index 0000000..67d5bb5 --- /dev/null +++ b/.changelog/c981d53176e142ba94d885a13e608faa.md @@ -0,0 +1,4 @@ +--- +type: minor +--- +Update geo-data, Türkiye \ No newline at end of file diff --git a/octodns/record/geo_data.py b/octodns/record/geo_data.py index d78eabf..dfbe75f 100644 --- a/octodns/record/geo_data.py +++ b/octodns/record/geo_data.py @@ -123,7 +123,7 @@ geo_data = { 'TJ': {'name': 'Tajikistan'}, 'TL': {'name': 'Timor-Leste'}, 'TM': {'name': 'Turkmenistan'}, - 'TR': {'name': 'Turkey'}, + 'TR': {'name': 'Türkiye'}, 'TW': {'name': 'Taiwan, Province of China'}, 'UZ': {'name': 'Uzbekistan'}, 'VN': {'name': 'Viet Nam'}, From 117c14cc9646fbac22216326150e2d3f4e696ca0 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 09:54:07 -0700 Subject: [PATCH 14/29] A pass at a cron action to auto-generate update PRs to geo-data --- .../72ba948357fb41ea948a25b8c8454131.md | 4 ++ .github/workflows/geo-data.yml | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .changelog/72ba948357fb41ea948a25b8c8454131.md create mode 100644 .github/workflows/geo-data.yml diff --git a/.changelog/72ba948357fb41ea948a25b8c8454131.md b/.changelog/72ba948357fb41ea948a25b8c8454131.md new file mode 100644 index 0000000..2e83193 --- /dev/null +++ b/.changelog/72ba948357fb41ea948a25b8c8454131.md @@ -0,0 +1,4 @@ +--- +type: none +--- +Cron action for geo-data.py \ No newline at end of file diff --git a/.github/workflows/geo-data.yml b/.github/workflows/geo-data.yml new file mode 100644 index 0000000..b6afa3a --- /dev/null +++ b/.github/workflows/geo-data.yml @@ -0,0 +1,55 @@ +name: Update geo_data.py +on: + workflow_dispatch: # option to run manually if/when needed + schedule: + - cron: "42 3 * * 6" # sat @ 3:42am + +jobs: + config: + runs-on: ubuntu-latest + outputs: + json: ${{ steps.load.outputs.json }} + steps: + - uses: actions/checkout@v4 + - id: load + # based on https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings + run: | + { + echo 'json<> $GITHUB_OUTPUT + update-geo-data: + needs: config + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ fromJson(needs.config.outputs.json).python_version_current }} + architecture: x64 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install virtualenv + - name: Generate geo_data.py + run: | + source env/bin/activate + ./script/generate-geo-data > octodns/record/geo_data.py + [ `git status --porcelain=1 | wc -l` -ne 0 ] && ./script/changelog create -t minor Periodic updates to geo_data.py + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.PAT }} + commit-message: Periodic updates to geo_data.py + committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + signoff: false + branch: update-geo-data + delete-branch: true + title: 'Update geo data to reflect recent changes' + body: Auto-generated with https://github.com/peter-evans/create-pull-request + draft: false From 2e082425857a90ff94471dec959786a0c682ee86 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 10:02:11 -0700 Subject: [PATCH 15/29] don't need to activate env, there isn't one --- .changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md | 4 ++++ .github/workflows/geo-data.yml | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md diff --git a/.changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md b/.changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md new file mode 100644 index 0000000..c637a10 --- /dev/null +++ b/.changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md @@ -0,0 +1,4 @@ +--- +type: none +--- +Fixes for geo-data.py action \ No newline at end of file diff --git a/.github/workflows/geo-data.yml b/.github/workflows/geo-data.yml index b6afa3a..02192b8 100644 --- a/.github/workflows/geo-data.yml +++ b/.github/workflows/geo-data.yml @@ -36,7 +36,6 @@ jobs: pip install virtualenv - name: Generate geo_data.py run: | - source env/bin/activate ./script/generate-geo-data > octodns/record/geo_data.py [ `git status --porcelain=1 | wc -l` -ne 0 ] && ./script/changelog create -t minor Periodic updates to geo_data.py - name: Create Pull Request From 9bd32e003fcf99a4ecaa4018e96abf1d5b49e695 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 10:07:30 -0700 Subject: [PATCH 16/29] actually forgot to bootstrap --- .github/workflows/geo-data.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/geo-data.yml b/.github/workflows/geo-data.yml index 02192b8..cecd768 100644 --- a/.github/workflows/geo-data.yml +++ b/.github/workflows/geo-data.yml @@ -36,6 +36,8 @@ jobs: pip install virtualenv - name: Generate geo_data.py run: | + ./script/bootstrap + source env/bin/activate ./script/generate-geo-data > octodns/record/geo_data.py [ `git status --porcelain=1 | wc -l` -ne 0 ] && ./script/changelog create -t minor Periodic updates to geo_data.py - name: Create Pull Request From f34d4e935435f3815d2c8099d850a5be6102d3b6 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 10:11:04 -0700 Subject: [PATCH 17/29] Avoid exit 1 if there are no changes --- .github/workflows/geo-data.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/geo-data.yml b/.github/workflows/geo-data.yml index cecd768..c682a51 100644 --- a/.github/workflows/geo-data.yml +++ b/.github/workflows/geo-data.yml @@ -39,7 +39,7 @@ jobs: ./script/bootstrap source env/bin/activate ./script/generate-geo-data > octodns/record/geo_data.py - [ `git status --porcelain=1 | wc -l` -ne 0 ] && ./script/changelog create -t minor Periodic updates to geo_data.py + [ `git status --porcelain=1 | wc -l` -ne 0 ] && ./script/changelog create -t minor Periodic updates to geo_data.py || true - name: Create Pull Request id: cpr uses: peter-evans/create-pull-request@v7 From f0af54e725823fa2e9cbab274cb1156c4838e9fe Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 10:23:52 -0700 Subject: [PATCH 18/29] print out the diff --- .github/workflows/geo-data.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/geo-data.yml b/.github/workflows/geo-data.yml index c682a51..30005f9 100644 --- a/.github/workflows/geo-data.yml +++ b/.github/workflows/geo-data.yml @@ -39,6 +39,7 @@ jobs: ./script/bootstrap source env/bin/activate ./script/generate-geo-data > octodns/record/geo_data.py + git diff [ `git status --porcelain=1 | wc -l` -ne 0 ] && ./script/changelog create -t minor Periodic updates to geo_data.py || true - name: Create Pull Request id: cpr From f2189b2149478b299cb28bc24961f254227cac0b Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 19 Jun 2025 10:24:16 -0700 Subject: [PATCH 19/29] simplify and clean up params for create pr step --- .github/workflows/geo-data.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/geo-data.yml b/.github/workflows/geo-data.yml index 30005f9..a1249ec 100644 --- a/.github/workflows/geo-data.yml +++ b/.github/workflows/geo-data.yml @@ -45,13 +45,7 @@ jobs: id: cpr uses: peter-evans/create-pull-request@v7 with: - token: ${{ secrets.PAT }} commit-message: Periodic updates to geo_data.py - committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> - author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> - signoff: false branch: update-geo-data - delete-branch: true - title: 'Update geo data to reflect recent changes' + title: Update geo_data.py to reflect recent changes body: Auto-generated with https://github.com/peter-evans/create-pull-request - draft: false From de96c94bde76c54f7edfc91e4d5eb13a01110f02 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 25 Jun 2025 13:34:25 -0700 Subject: [PATCH 20/29] update requirements*.txt --- .../ad02e7c6359a43f59a92cf53c8b13e8c.md | 4 ++ requirements-dev.txt | 57 ++++++++++--------- requirements.txt | 2 +- script/changelog | 9 +++ 4 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 .changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md diff --git a/.changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md b/.changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md new file mode 100644 index 0000000..57b89f2 --- /dev/null +++ b/.changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md @@ -0,0 +1,4 @@ +--- +type: none +--- +requirements-dev.txt requirements.txt \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index b2e7d80..6fbe6f0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,48 +1,49 @@ # DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update -Pygments==2.18.0 +Pygments==2.19.2 +SecretStorage==3.3.3 black==24.10.0 build==1.2.2.post1 -certifi==2024.8.30 +certifi==2025.6.15 cffi==1.17.1 -charset-normalizer==3.3.2 -click==8.1.7 -cmarkgfm==2024.1.14 -coverage==7.6.1 +charset-normalizer==3.4.2 +click==8.2.1 +cmarkgfm==2024.11.20 +coverage==7.9.1 +cryptography==45.0.4 docutils==0.21.2 -importlib_metadata==8.5.0 -iniconfig==2.0.0 -isort==5.13.2 +id==1.5.0 +iniconfig==2.1.0 +isort==6.0.1 jaraco.classes==3.4.0 jaraco.context==6.0.1 -jaraco.functools==4.1.0 -keyring==25.4.1 +jaraco.functools==4.2.1 +jeepney==0.9.0 +keyring==25.6.0 markdown-it-py==3.0.0 mdurl==0.1.2 -more-itertools==10.5.0 -mypy-extensions==1.0.0 -nh3==0.2.18 -packaging==24.1 +more-itertools==10.7.0 +mypy_extensions==1.1.0 +nh3==0.2.21 +packaging==25.0 pathspec==0.12.1 -pkginfo==1.10.0 -platformdirs==4.3.6 -pluggy==1.5.0 +platformdirs==4.3.8 +pluggy==1.6.0 pprintpp==0.4.0 pycountry-convert==0.7.2 pycountry==24.6.1 pycparser==2.22 -pyflakes==3.2.0 +pyflakes==3.4.0 pyproject_hooks==1.2.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest==8.3.3 +pytest-cov==6.2.1 +pytest-mock==3.14.1 +pytest==8.4.1 pytest_network==0.0.1 readme_renderer==44.0 repoze.lru==0.7 requests-toolbelt==1.0.0 -requests==2.32.3 +requests==2.32.4 rfc3986==2.0.0 -rich==13.9.2 -twine==5.1.1 -urllib3==2.2.3 -wheel==0.44.0 -zipp==3.20.2 +rich==14.0.0 +twine==6.1.0 +urllib3==2.5.0 +wheel==0.45.1 diff --git a/requirements.txt b/requirements.txt index dfef368..a40f955 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ fqdn==1.5.1 idna==3.10 natsort==8.4.0 python-dateutil==2.9.0.post0 -six==1.16.0 +six==1.17.0 diff --git a/script/changelog b/script/changelog index 0c7dd42..4623dc8 100755 --- a/script/changelog +++ b/script/changelog @@ -39,6 +39,12 @@ def create(argv): See https://semver.org/ for more info''', ) + parser.add_argument( + '-a', + '--add', + default=False, + help='git add the newly created changelog entry', + ) parser.add_argument( 'md', metavar='change-description-markdown', @@ -63,6 +69,9 @@ and links.''', f'Created {filepath}, it can be further edited and should be committed to your branch.' ) + if args.add: + run(['git', 'add', filepath]) + def check(argv): if isdir('.changelog'): From 6d5574a2e00025573179248ab561b3ff56ba6050 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 16 Jun 2025 18:24:21 -0700 Subject: [PATCH 21/29] Special handling for click w/python 3.9 --- script/update-requirements | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/script/update-requirements b/script/update-requirements index 7696f6d..c1adc47 100755 --- a/script/update-requirements +++ b/script/update-requirements @@ -42,6 +42,18 @@ dev_frozen = sorted( [p for p in dev_frozen if not p.startswith(our_package_name)] ) +# special handling for click until python 3.9 is gone due to it dropping +# support for active versions early +i = [i for i, r in enumerate(dev_frozen) if r.startswith('click==')][0] +dev_frozen = ( + dev_frozen[:i] + + [ + "click==8.1.8; python_version<'3.10'", + f"{dev_frozen[i]}; python_version>='3.10'", + ] + + dev_frozen[i + 1 :] +) + print_packages(frozen, 'frozen') print_packages(dev_frozen, 'dev_frozen') From 11deb858800968800d250604554ba8635a64fede Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 25 Jun 2025 13:38:45 -0700 Subject: [PATCH 22/29] click requirements-dev.txt update --- requirements-dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 6fbe6f0..3f5ee4e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,8 @@ build==1.2.2.post1 certifi==2025.6.15 cffi==1.17.1 charset-normalizer==3.4.2 -click==8.2.1 +click==8.1.8; python_version<'3.10' +click==8.2.1; python_version>='3.10' cmarkgfm==2024.11.20 coverage==7.9.1 cryptography==45.0.4 From cd4ee5f854b5e1dc27493cea6b17b576ac741f28 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 25 Jun 2025 15:09:13 -0700 Subject: [PATCH 23/29] changelog script improvements, manual pr, sort order, formatting --- .../ff12d7ab1c614a15b3ce8c6bba3407fb.md | 5 +++ CHANGELOG.md | 5 --- script/changelog | 38 +++++++++++++++---- 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 .changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md diff --git a/.changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md b/.changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md new file mode 100644 index 0000000..2a6bc1d --- /dev/null +++ b/.changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md @@ -0,0 +1,5 @@ +--- +type: minor +pr: 1251 +--- +Correct type-o in name of AcmeManagingProcessor, backwards compatible alias in place \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df45b4..d69254e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,3 @@ -## v1.11.? - 2025-??-?? - ??? - -* Correct type-o in name of AcmeManagingProcessor, backwards compatible alias - in place - ## v1.11.0 - 2025-02-03 - Cleanup & deprecations with meta planning * Deprecation warning for Source.populate w/o the lenient param, to be removed diff --git a/script/changelog b/script/changelog index 4623dc8..dc622f4 100755 --- a/script/changelog +++ b/script/changelog @@ -11,7 +11,7 @@ from subprocess import PIPE, run from sys import argv, exit, path, stderr from uuid import uuid4 -from yaml import safe_load_all +from yaml import safe_load def create(argv): @@ -39,6 +39,11 @@ def create(argv): See https://semver.org/ for more info''', ) + parser.add_argument( + '-p', + '--pr', + help='Manually override the PR number for the change, maintainer use only.', + ) parser.add_argument( '-a', '--add', @@ -62,6 +67,9 @@ and links.''', with open(filepath, 'w') as fh: fh.write('---\ntype: ') fh.write(args.type) + if args.pr: + fh.write('\npr: ') + fh.write(args.pr) fh.write('\n---\n') fh.write(' '.join(args.md)) @@ -106,7 +114,7 @@ class _ChangeMeta: _pr_cache = None @classmethod - def get(cls, filepath): + def get(cls, filepath, data): if cls._pr_cache is None: result = run( [ @@ -139,7 +147,13 @@ class _ChangeMeta: try: return cls._pr_cache[filepath] except KeyError: - return None, datetime(year=1970, month=1, day=1) + # couldn't find a PR with the changelog file in it + try: + # if a PR number was specified in the changelog entry use it + return data['pr'], datetime(year=1970, month=1, day=1) + except KeyError: + # otherwise just give up + return None, datetime(year=1970, month=1, day=1) def _get_changelogs(): @@ -150,8 +164,12 @@ def _get_changelogs(): continue filepath = join(dirname, filename) with open(filepath) as fh: - data, md = safe_load_all(fh) - pr, time = _ChangeMeta.get(filepath) + pieces = fh.read().split('---\n') + data = safe_load(pieces[1]) + md = pieces[2] + if md[-1] == '\n': + md = md[:-1] + pr, time = _ChangeMeta.get(filepath, data) if not pr: continue ret.append( @@ -165,7 +183,7 @@ def _get_changelogs(): ) ordering = {'major': 0, 'minor': 1, 'patch': 2, 'none': 3, '': 3} - ret.sort(key=lambda c: (ordering[c['type']], c['time'])) + ret.sort(key=lambda c: (ordering[c['type']], c['time']), reverse=True) return ret @@ -204,6 +222,9 @@ def bump(argv): action='store_true', help='Write changelog update and bump version number', ) + parser.add_argument( + 'title', nargs='+', help='A short title/quip for the release title' + ) args = parser.parse_args(argv) @@ -224,7 +245,7 @@ def bump(argv): buf.write(' - ') buf.write(datetime.now().strftime('%Y-%m-%d')) buf.write(' - ') - buf.write(' '.join(argv[1:])) + buf.write(' '.join(args.title)) buf.write('\n') current_type = None @@ -234,6 +255,9 @@ def bump(argv): continue _type = changelog['type'] + if _type == 'none': + # these aren't included in the listing + continue if _type != current_type: buf.write('\n') buf.write(_type.capitalize()) From 0fe8519167871959901eda161cb63c54e8d74031 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 25 Jun 2025 15:26:05 -0700 Subject: [PATCH 24/29] Fix sorting of changelog entries --- script/changelog | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/script/changelog b/script/changelog index dc622f4..046c3ea 100755 --- a/script/changelog +++ b/script/changelog @@ -115,6 +115,8 @@ class _ChangeMeta: @classmethod def get(cls, filepath, data): + if 'pr' in data: + return data['pr'], datetime(year=1970, month=1, day=1) if cls._pr_cache is None: result = run( [ @@ -148,12 +150,7 @@ class _ChangeMeta: return cls._pr_cache[filepath] except KeyError: # couldn't find a PR with the changelog file in it - try: - # if a PR number was specified in the changelog entry use it - return data['pr'], datetime(year=1970, month=1, day=1) - except KeyError: - # otherwise just give up - return None, datetime(year=1970, month=1, day=1) + return None, datetime(year=1970, month=1, day=1) def _get_changelogs(): @@ -182,7 +179,7 @@ def _get_changelogs(): } ) - ordering = {'major': 0, 'minor': 1, 'patch': 2, 'none': 3, '': 3} + ordering = {'major': 3, 'minor': 2, 'patch': 1, 'none': 0, '': 0} ret.sort(key=lambda c: (ordering[c['type']], c['time']), reverse=True) return ret From f1b09ded98d9a7bc35ca962c31cf2ead25f5836e Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 25 Jun 2025 15:37:44 -0700 Subject: [PATCH 25/29] Version 1.12.0 version bump and changelog update --- .changelog/0c0c814add3c4ce5983eb3a6d0430de4.md | 4 ---- .changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md | 4 ---- .changelog/44274039f47d4df0946a45fb9c228d3f.md | 4 ---- .changelog/4af5a11fb21842ffb627d5ee4d80fb14.md | 4 ---- .changelog/72ba948357fb41ea948a25b8c8454131.md | 4 ---- .changelog/acae8de42da4482ea06e380586010eb8.md | 4 ---- .changelog/acc2596fb367494db070e6c06abf705a.md | 4 ---- .changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md | 4 ---- .changelog/c981d53176e142ba94d885a13e608faa.md | 4 ---- .changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md | 5 ----- CHANGELOG.md | 8 ++++++++ octodns/__init__.py | 2 +- 12 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 .changelog/0c0c814add3c4ce5983eb3a6d0430de4.md delete mode 100644 .changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md delete mode 100644 .changelog/44274039f47d4df0946a45fb9c228d3f.md delete mode 100644 .changelog/4af5a11fb21842ffb627d5ee4d80fb14.md delete mode 100644 .changelog/72ba948357fb41ea948a25b8c8454131.md delete mode 100644 .changelog/acae8de42da4482ea06e380586010eb8.md delete mode 100644 .changelog/acc2596fb367494db070e6c06abf705a.md delete mode 100644 .changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md delete mode 100644 .changelog/c981d53176e142ba94d885a13e608faa.md delete mode 100644 .changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md diff --git a/.changelog/0c0c814add3c4ce5983eb3a6d0430de4.md b/.changelog/0c0c814add3c4ce5983eb3a6d0430de4.md deleted file mode 100644 index 24340ae..0000000 --- a/.changelog/0c0c814add3c4ce5983eb3a6d0430de4.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: minor ---- -New provider: Bunny DNS \ No newline at end of file diff --git a/.changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md b/.changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md deleted file mode 100644 index c637a10..0000000 --- a/.changelog/3e80c9a9167b4d0ea3eb95f8b46b177a.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: none ---- -Fixes for geo-data.py action \ No newline at end of file diff --git a/.changelog/44274039f47d4df0946a45fb9c228d3f.md b/.changelog/44274039f47d4df0946a45fb9c228d3f.md deleted file mode 100644 index e81e06f..0000000 --- a/.changelog/44274039f47d4df0946a45fb9c228d3f.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: none ---- -update markdown tables \ No newline at end of file diff --git a/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md b/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md deleted file mode 100644 index ece6385..0000000 --- a/.changelog/4af5a11fb21842ffb627d5ee4d80fb14.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: minor ---- -Templating processor added diff --git a/.changelog/72ba948357fb41ea948a25b8c8454131.md b/.changelog/72ba948357fb41ea948a25b8c8454131.md deleted file mode 100644 index 2e83193..0000000 --- a/.changelog/72ba948357fb41ea948a25b8c8454131.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: none ---- -Cron action for geo-data.py \ No newline at end of file diff --git a/.changelog/acae8de42da4482ea06e380586010eb8.md b/.changelog/acae8de42da4482ea06e380586010eb8.md deleted file mode 100644 index 8c7aeb5..0000000 --- a/.changelog/acae8de42da4482ea06e380586010eb8.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: none ---- -fix markdown format \ No newline at end of file diff --git a/.changelog/acc2596fb367494db070e6c06abf705a.md b/.changelog/acc2596fb367494db070e6c06abf705a.md deleted file mode 100644 index 2f3525b..0000000 --- a/.changelog/acc2596fb367494db070e6c06abf705a.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: none ---- -Adding changelog management infra and doc \ No newline at end of file diff --git a/.changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md b/.changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md deleted file mode 100644 index 57b89f2..0000000 --- a/.changelog/ad02e7c6359a43f59a92cf53c8b13e8c.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: none ---- -requirements-dev.txt requirements.txt \ No newline at end of file diff --git a/.changelog/c981d53176e142ba94d885a13e608faa.md b/.changelog/c981d53176e142ba94d885a13e608faa.md deleted file mode 100644 index 67d5bb5..0000000 --- a/.changelog/c981d53176e142ba94d885a13e608faa.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -type: minor ---- -Update geo-data, Türkiye \ No newline at end of file diff --git a/.changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md b/.changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md deleted file mode 100644 index 2a6bc1d..0000000 --- a/.changelog/ff12d7ab1c614a15b3ce8c6bba3407fb.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: minor -pr: 1251 ---- -Correct type-o in name of AcmeManagingProcessor, backwards compatible alias in place \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d69254e..7eadfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.12.0 - 2025-06-25 - Automated changelogs + +Minor: +* Templating processor added [#1259](https://github.com/octodns/octodns/pull/1259) +* Update geo-data, Türkiye [#1263](https://github.com/octodns/octodns/pull/1263) +* New provider: Bunny DNS [#1262](https://github.com/octodns/octodns/pull/1262) +* Correct type-o in name of AcmeManagingProcessor, backwards compatible alias in place [#1251](https://github.com/octodns/octodns/pull/1251) + ## v1.11.0 - 2025-02-03 - Cleanup & deprecations with meta planning * Deprecation warning for Source.populate w/o the lenient param, to be removed diff --git a/octodns/__init__.py b/octodns/__init__.py index d49222c..5340737 100644 --- a/octodns/__init__.py +++ b/octodns/__init__.py @@ -1,4 +1,4 @@ 'OctoDNS: DNS as code - Tools for managing DNS across multiple providers' # TODO: remove __VERSION__ w/2.x -__version__ = __VERSION__ = '1.11.0' +__version__ = __VERSION__ = '1.12.0' From 68fd1b3b2d01c2abfa82aeef4f753fbb4491a08e Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 25 Jun 2025 15:47:36 -0700 Subject: [PATCH 26/29] Small fix for script release --- .changelog/80f748502d3e4258bcbf4a05d5f5bdb0.md | 4 ++++ script/changelog | 1 + script/release | 4 ++++ 3 files changed, 9 insertions(+) create mode 100644 .changelog/80f748502d3e4258bcbf4a05d5f5bdb0.md diff --git a/.changelog/80f748502d3e4258bcbf4a05d5f5bdb0.md b/.changelog/80f748502d3e4258bcbf4a05d5f5bdb0.md new file mode 100644 index 0000000..fbf6c6d --- /dev/null +++ b/.changelog/80f748502d3e4258bcbf4a05d5f5bdb0.md @@ -0,0 +1,4 @@ +--- +type: none +--- +Create dist directory in scripte/release if it doesn't exist \ No newline at end of file diff --git a/script/changelog b/script/changelog index 046c3ea..a2a63bb 100755 --- a/script/changelog +++ b/script/changelog @@ -47,6 +47,7 @@ See https://semver.org/ for more info''', parser.add_argument( '-a', '--add', + action='store_true', default=False, help='git add the newly created changelog entry', ) diff --git a/script/release b/script/release index da5d216..1fab0eb 100755 --- a/script/release +++ b/script/release @@ -44,6 +44,10 @@ echo "Created clean room $TMP_DIR and archived $VERSION into it" (cd "$TMP_DIR" && python -m build --sdist --wheel) +if [ ! -d dist ]; then + mkdir dist/ +fi + cp $TMP_DIR/dist/*$VERSION.tar.gz $TMP_DIR/dist/*$VERSION*.whl dist/ echo "Copied $TMP_DIR/dists into ./dist" From 281f89e826290bc723ce1ebe81fc978ff74fdd8d Mon Sep 17 00:00:00 2001 From: Gavin Chappell <2798739+g-a-c@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:55:43 +0100 Subject: [PATCH 27/29] Add basic documentation on configuring/utilising octoDNS processors --- docs/processors.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/processors.md diff --git a/docs/processors.md b/docs/processors.md new file mode 100644 index 0000000..c8297e7 --- /dev/null +++ b/docs/processors.md @@ -0,0 +1,59 @@ +# octoDNS processors + +## Available processors + +These are listed in the main [`README`](../README.md#processors) + +## Configuring processors + +Configuring processors is done in the main config file. + +### Defining a processor configuration + +This is done under the top-level `processors` key in the octoDNS config file (for example `config.yaml`), as a sibling to the `manager` key. + +The `processors` key contains YAML objects, where the key is the name of the processor, and the `class` value within that object refers to the processor name. + +For example, to define a provider called `custom_meta` using the [`MetaProcessor`](../octodns/processor/meta.py) in order to extend the default `include_meta` behaviour: + +```yaml +manager: + include_meta: false # disable default, basic `meta` records +processors: + custom_meta: + class: octodns.processor.meta.MetaProcessor + record_name: meta + include_time: true + include_uuid: true + include_provider: true + include_version: false +``` + +**NOTE:** the specific parameters for each processor are only documented within [the code](../octodns/processor/) + +### Utilising the processor configuration + +#### On **individual** domains + +Each domain can utilise the processor independently by adding the name of the defined processor to a `processors` key beneath a `zone`: + +```yaml +zones: + "example.com": + source: + - yaml_config + target: + - hetzner + processors: + - custom_meta +``` + +#### On **all** domains + +To utilise the processor on **all** domains automatically, including new domains added to the `zones` config in future then you can add this to the `processors` key under the `manager` section of the configuration: + +```yaml +manager: + processors: + - custom_meta +``` From 8f22b0729a4eaa433f099af45ec5e9ec3195f55e Mon Sep 17 00:00:00 2001 From: Gavin Chappell <2798739+g-a-c@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:10:06 +0100 Subject: [PATCH 28/29] fix a typo in the example --- docs/processors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/processors.md b/docs/processors.md index c8297e7..70ee8f7 100644 --- a/docs/processors.md +++ b/docs/processors.md @@ -39,7 +39,7 @@ Each domain can utilise the processor independently by adding the name of the de ```yaml zones: - "example.com": + example.com.: source: - yaml_config target: From 905f109c7be5cb3cf5247d1e75a65ded4b0fdea6 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Fri, 27 Jun 2025 12:46:48 -0700 Subject: [PATCH 29/29] Changelog entry for processors doc --- .changelog/c4f025d1c23c40dd98380e6d3496364d.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/c4f025d1c23c40dd98380e6d3496364d.md diff --git a/.changelog/c4f025d1c23c40dd98380e6d3496364d.md b/.changelog/c4f025d1c23c40dd98380e6d3496364d.md new file mode 100644 index 0000000..e8a2d91 --- /dev/null +++ b/.changelog/c4f025d1c23c40dd98380e6d3496364d.md @@ -0,0 +1,4 @@ +--- +type: none +--- +Documentation for processors \ No newline at end of file