diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bdcdda..23da685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## v1.?.? - 2024-??-?? - ??? +* Allow DS records to be specified for managed sub-zones, same as NS * Fix CAA rdata parsing to allow values with tags * Improve TXT (and SPF) record handling of unexpected whitespace before/after/in-between quotes. diff --git a/docs/records.md b/docs/records.md index e3b878e..d5f2cc4 100644 --- a/docs/records.md +++ b/docs/records.md @@ -10,6 +10,7 @@ octoDNS supports the following record types: * `CAA` * `CNAME` * `DNAME` +* `DS` * `LOC` * `MX` * `NAPTR` diff --git a/octodns/zone.py b/octodns/zone.py index 4ac7823..2a623ae 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -150,10 +150,10 @@ class Zone(object): if not lenient: if name in self.sub_zones: # It's an exact match for a sub-zone - if not record._type == 'NS': - # and not a NS record, this should be in the sub + if not (record._type == 'NS' or record._type == 'DS'): + # and not a NS or DS record, this should be in the sub raise SubzoneRecordException( - f'Record {record.fqdn} is a managed sub-zone and not of type NS', + f'Record {record.fqdn} is a managed sub-zone and not of type NS or DS', record, ) else: diff --git a/requirements-dev.txt b/requirements-dev.txt index 2598143..d5aed31 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,21 +1,21 @@ # DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update -Pygments==2.17.2 -black==24.4.0 +Pygments==2.18.0 +black==24.4.2 build==1.2.1 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 click==8.1.7 cmarkgfm==2024.1.14 -coverage==7.4.4 +coverage==7.5.3 docutils==0.20.1 importlib_metadata==7.1.0 iniconfig==2.0.0 isort==5.13.2 jaraco.classes==3.4.0 jaraco.context==5.3.0 -jaraco.functools==4.0.0 -keyring==25.1.0 +jaraco.functools==4.0.1 +keyring==25.2.1 markdown-it-py==3.0.0 mdurl==0.1.2 more-itertools==10.2.0 @@ -24,25 +24,25 @@ nh3==0.2.17 packaging==24.0 pathspec==0.12.1 pkginfo==1.10.0 -platformdirs==4.2.0 -pluggy==1.4.0 +platformdirs==4.2.2 +pluggy==1.5.0 pprintpp==0.4.0 pycountry-convert==0.7.2 pycountry==23.12.11 pycparser==2.22 pyflakes==3.2.0 -pyproject_hooks==1.0.0 +pyproject_hooks==1.1.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-network==0.0.1 -pytest==8.1.1 +pytest==8.2.1 readme_renderer==43.0 repoze.lru==0.7 requests-toolbelt==1.0.0 -requests==2.31.0 +requests==2.32.2 rfc3986==2.0.0 rich==13.7.1 -twine==5.0.0 +twine==5.1.0 urllib3==2.2.1 wheel==0.43.0 -zipp==3.18.1 +zipp==3.19.0 diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index a01b6e9..735a0b0 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -346,6 +346,91 @@ class TestZone(TestCase): zone.add_record(record) self.assertEqual(1, len(zone.records)) + # DS record for exactly the sub is allowed + zone = Zone('unit.tests.', set(['sub', 'barred'])) + record = Record.new( + zone, + 'sub', + { + 'ttl': 3600, + 'type': 'DS', + 'values': [ + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292118', + }, + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292119', + }, + ], + }, + ) + zone.add_record(record) + self.assertEqual(set([record]), zone.records) + + # DS record for something below the sub is rejected + zone = Zone('unit.tests.', set(['sub', 'barred'])) + record = Record.new( + zone, + 'foo.bar.sub', + { + 'ttl': 3600, + 'type': 'DS', + 'values': [ + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292118', + }, + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292119', + }, + ], + }, + ) + with self.assertRaises(SubzoneRecordException) as ctx: + zone.add_record(record) + self.assertTrue('under a managed sub-zone', str(ctx.exception)) + # Can add it w/lenient + zone.add_record(record, lenient=True) + self.assertEqual(set([record]), zone.records) + + # DS record that happens to end with a string that matches a sub (no .) is OK + zone = Zone('unit.tests.', set(['sub', 'barred'])) + record = Record.new( + zone, + 'foo.bar_sub', + { + 'ttl': 3600, + 'type': 'DS', + 'values': [ + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292118', + }, + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292119', + }, + ], + }, + ) + zone.add_record(record) + self.assertEqual(1, len(zone.records)) + def test_ignored_records(self): zone_normal = Zone('unit.tests.', []) zone_ignored = Zone('unit.tests.', [])