From 9d31ce7ed8c01e2690a0bee843ca83f376963929 Mon Sep 17 00:00:00 2001 From: Timothy Berger <5579958+rasturic@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:57:08 -1000 Subject: [PATCH] Add DS record support --- CHANGELOG.md | 1 + README.md | 2 +- octodns_route53/provider.py | 28 +++++++++++++++ tests/test_octodns_provider_route53.py | 49 +++++++++++++++++++------- 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f225533..f50f9d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Important * Add `append_to_names` tag append parameter to sources +* Add `DS` record type support ## v0.0.6 - 2023-10-16 - Long overdue diff --git a/README.md b/README.md index c2ad7a7..7110b00 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ zones: #### Records -A, AAAA, CAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, TXT +A, AAAA, CAA, CNAME, DS, MX, NAPTR, NS, PTR, SPF, SRV, TXT #### Root NS Records diff --git a/octodns_route53/provider.py b/octodns_route53/provider.py index 29e01d7..ee50e86 100644 --- a/octodns_route53/provider.py +++ b/octodns_route53/provider.py @@ -312,9 +312,15 @@ def _values_for_quoted(self, record): def _value_for_SRV(self, value, record): return f'{value.priority} {value.weight} {value.port} {value.target}' + def _value_for_DS(self, value, record): + return f'{value.key_tag} {value.algorithm} {value.digest_type} {value.digest}' + def _values_for_SRV(self, record): return [self._value_for_SRV(v, record) for v in record.values] + def _values_for_DS(self, record): + return [self._value_for_DS(v, record) for v in record.values] + class _Route53Alias(_Route53Record): def __init__(self, provider, hosted_zone_id, record, value, creating): @@ -752,6 +758,7 @@ class Route53Provider(_AuthMixin, BaseProvider): 'AAAA', 'CAA', 'CNAME', + 'DS', 'MX', 'NAPTR', 'NS', @@ -1000,6 +1007,27 @@ def _data_for_SRV(self, rrset): 'ttl': int(rrset['TTL']), } + def _data_for_DS(self, rrset): + values = [] + for rr in rrset['ResourceRecords']: + # digest may contain whitespace + key_tag, algorithm, digest_type, digest = rr['Value'].split( + maxsplit=3 + ) + values.append( + { + 'key_tag': key_tag, + 'algorithm': algorithm, + 'digest_type': digest_type, + 'digest': digest, + } + ) + return { + 'type': rrset['Type'], + 'values': values, + 'ttl': int(rrset['TTL']), + } + def _load_records(self, zone_id): if zone_id not in self._r53_rrsets: self.log.debug('_load_records: zone_id=%s loading', zone_id) diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index 0b08885..15bfe2f 100644 --- a/tests/test_octodns_provider_route53.py +++ b/tests/test_octodns_provider_route53.py @@ -404,6 +404,21 @@ class TestRoute53Provider(TestCase): 'value': {'flags': 0, 'tag': 'issue', 'value': 'ca.unit.tests'}, }, ), + ( + 'dskey', + { + 'ttl': 70, + 'type': 'DS', + 'values': [ + { + 'key_tag': 60485, + 'algorithm': 5, + 'digest_type': 1, + 'digest': '2BB183AF5F22588179A53B0A 98631FAD1A292118', + } + ], + }, + ), ( 'alias', { @@ -1114,6 +1129,16 @@ def test_populate(self): 'ResourceRecords': [{'Value': '0 issue "ca.unit.tests"'}], 'TTL': 69, }, + { + 'Name': 'dskey.unit.tests.', + 'Type': 'DS', + 'ResourceRecords': [ + { + 'Value': '60485 5 1 2BB183AF5F22588179A53B0A 98631FAD1A292118' + } + ], + 'TTL': 70, + }, { 'AliasTarget': { 'HostedZoneId': 'Z119WBBTVP5WFX', @@ -1243,7 +1268,7 @@ def test_sync(self): ) plan = provider.plan(self.expected) - self.assertEqual(12, len(plan.changes)) + self.assertEqual(13, len(plan.changes)) self.assertTrue(plan.exists) for change in plan.changes: self.assertIsInstance(change, Create) @@ -1270,7 +1295,7 @@ def test_sync(self): {'HostedZoneId': 'z42', 'ChangeBatch': ANY}, ) - self.assertEqual(12, provider.apply(plan)) + self.assertEqual(13, provider.apply(plan)) stubber.assert_no_pending_responses() # Delete by monkey patching in a populate that includes an extra record @@ -1536,7 +1561,7 @@ def test_sync_create(self): stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {}) plan = provider.plan(self.expected) - self.assertEqual(12, len(plan.changes)) + self.assertEqual(13, len(plan.changes)) self.assertFalse(plan.exists) for change in plan.changes: self.assertIsInstance(change, Create) @@ -1608,7 +1633,7 @@ def test_sync_create(self): {'HostedZoneId': 'z42', 'ChangeBatch': ANY}, ) - self.assertEqual(12, provider.apply(plan)) + self.assertEqual(13, provider.apply(plan)) stubber.assert_no_pending_responses() def test_sync_create_with_delegation_set(self): @@ -1625,7 +1650,7 @@ def test_sync_create_with_delegation_set(self): stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {}) plan = provider.plan(self.expected) - self.assertEqual(12, len(plan.changes)) + self.assertEqual(13, len(plan.changes)) self.assertFalse(plan.exists) for change in plan.changes: self.assertIsInstance(change, Create) @@ -1701,7 +1726,7 @@ def test_sync_create_with_delegation_set(self): {'HostedZoneId': 'z42', 'ChangeBatch': ANY}, ) - self.assertEqual(12, provider.apply(plan)) + self.assertEqual(13, provider.apply(plan)) stubber.assert_no_pending_responses() def test_health_checks_pagination(self): @@ -2641,7 +2666,7 @@ def test_plan_apply_with_get_zones_by_name_zone_not_exists(self): ) plan = provider.plan(self.expected) - self.assertEqual(12, len(plan.changes)) + self.assertEqual(13, len(plan.changes)) create_hosted_zone_resp = { 'HostedZone': { @@ -2709,7 +2734,7 @@ def test_plan_apply_with_get_zones_by_name_zone_not_exists(self): {'HostedZoneId': 'z42', 'ChangeBatch': ANY}, ) - self.assertEqual(12, provider.apply(plan)) + self.assertEqual(13, provider.apply(plan)) stubber.assert_no_pending_responses() def test_plan_apply_with_get_zones_by_name_zone_exists(self): @@ -2760,7 +2785,7 @@ def test_plan_apply_with_get_zones_by_name_zone_exists(self): ) plan = provider.plan(self.expected) - self.assertEqual(13, len(plan.changes)) + self.assertEqual(14, len(plan.changes)) stubber.add_response( 'list_health_checks', @@ -2784,7 +2809,7 @@ def test_plan_apply_with_get_zones_by_name_zone_exists(self): {'HostedZoneId': 'z42', 'ChangeBatch': ANY}, ) - self.assertEqual(13, provider.apply(plan)) + self.assertEqual(14, provider.apply(plan)) stubber.assert_no_pending_responses() def test_extra_change_no_health_check(self): @@ -3450,7 +3475,7 @@ def _get_test_plan(self, max_changes): @patch('octodns_route53.Route53Provider._really_apply') def test_apply_1(self, really_apply_mock, _): # 18 RRs with max of 19 should only get applied in one call - provider, plan = self._get_test_plan(19) + provider, plan = self._get_test_plan(20) provider.apply(plan) really_apply_mock.assert_called_once() @@ -3468,7 +3493,7 @@ def test_apply_3(self, really_apply_mock, _): # with a max of seven modifications, three calls provider, plan = self._get_test_plan(7) provider.apply(plan) - self.assertEqual(3, really_apply_mock.call_count) + self.assertEqual(4, really_apply_mock.call_count) @patch('octodns_route53.Route53Provider._load_records') @patch('octodns_route53.Route53Provider._really_apply')