From 857d5e2a61c14c720fa1e9a69440de9d84072fad Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Fri, 18 Aug 2023 20:24:26 -0700 Subject: [PATCH 1/6] Record's carry context, Zone exceptions make use of it to help with error messages --- octodns/record/base.py | 17 ++++--- octodns/zone.py | 52 +++++++++++++++---- tests/test_octodns_provider_yaml.py | 18 ++++--- tests/test_octodns_record.py | 10 ++++ tests/test_octodns_zone.py | 79 ++++++++++++++++++++++++++++- 5 files changed, 154 insertions(+), 22 deletions(-) diff --git a/octodns/record/base.py b/octodns/record/base.py index ffea1b6..a1b26b9 100644 --- a/octodns/record/base.py +++ b/octodns/record/base.py @@ -5,6 +5,7 @@ from collections import defaultdict from logging import getLogger +from ..context import ContextDict from ..equality import EqualityTupleMixin from ..idna import IdnaError, idna_decode, idna_encode from .change import Update @@ -73,7 +74,7 @@ class Record(EqualityTupleMixin): ) else: raise ValidationError(fqdn, reasons, context) - return _class(zone, name, data, source=source) + return _class(zone, name, data, source=source, context=context) @classmethod def validate(cls, name, fqdn, data): @@ -136,7 +137,7 @@ class Record(EqualityTupleMixin): def parse_rdata_texts(cls, rdatas): return [cls._value_type.parse_rdata_text(r) for r in rdatas] - def __init__(self, zone, name, data, source=None): + def __init__(self, zone, name, data, source=None, context=None): self.zone = zone if name: # internally everything is idna @@ -152,11 +153,14 @@ class Record(EqualityTupleMixin): self.decoded_name, ) self.source = source + self.context = context self.ttl = int(data['ttl']) self._octodns = data.get('octodns', {}) def _data(self): + if self.context: + return ContextDict({'ttl': self.ttl}, context=self.context) return {'ttl': self.ttl} @property @@ -225,6 +229,7 @@ class Record(EqualityTupleMixin): return Update(self, other) def copy(self, zone=None): + # data, via _data(), will preserve context data = self.data data['type'] = self._type data['octodns'] = self._octodns @@ -271,8 +276,8 @@ class ValuesMixin(object): values = [cls._value_type.parse_rdata_text(rr.rdata) for rr in rrs] return {'ttl': rr.ttl, 'type': rr._type, 'values': values} - def __init__(self, zone, name, data, source=None): - super().__init__(zone, name, data, source=source) + def __init__(self, zone, name, data, source=None, context=None): + super().__init__(zone, name, data, source=source, context=context) try: values = data['values'] except KeyError: @@ -333,8 +338,8 @@ class ValueMixin(object): 'value': cls._value_type.parse_rdata_text(rr.rdata), } - def __init__(self, zone, name, data, source=None): - super().__init__(zone, name, data, source=source) + def __init__(self, zone, name, data, source=None, context=None): + super().__init__(zone, name, data, source=source, context=context) self.value = self._value_type.process(data['value']) def changes(self, other, target): diff --git a/octodns/zone.py b/octodns/zone.py index 8855986..a1ee51d 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -11,15 +11,45 @@ from .record import Create, Delete class SubzoneRecordException(Exception): - pass + def __init__(self, msg, record): + self.record = record + + if record.context: + msg += f', {record.context}' + + super().__init__(msg) class DuplicateRecordException(Exception): - pass + def __init__(self, msg, existing, new): + self.existing = existing + self.new = new + + if existing.context: + if new.context: + # both have context + msg += f'\n existing: {existing.context}\n new: {new.context}' + else: + # only existing has context + msg += ( + f'\n existing: {existing.context}\n new: [UNKNOWN]' + ) + elif new.context: + # only new has context + msg += f'\n existing: [UNKNOWN]\n new: {new.context}' + # else no one has context + + super().__init__(msg) class InvalidNodeException(Exception): - pass + def __init__(self, msg, record): + self.record = record + + if record.context: + msg += f', {record.context}' + + super().__init__(msg) class Zone(object): @@ -113,7 +143,8 @@ class Zone(object): if not record._type == 'NS': # and not a NS 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', + record, ) else: # It's not an exact match so there has to be a `.` before the @@ -122,7 +153,8 @@ class Zone(object): if name.endswith(f'.{sub_zone}'): # this should be in a sub raise SubzoneRecordException( - f'Record {record.fqdn} is under a managed subzone' + f'Record {record.fqdn} is under a managed subzone', + record, ) if replace: @@ -132,8 +164,11 @@ class Zone(object): node = self._records[name] if record in node: # We already have a record at this node of this type + existing = [c for c in node if c == record][0] raise DuplicateRecordException( - f'Duplicate record {record.fqdn}, ' f'type {record._type}' + f'Duplicate record {record.fqdn}, type {record._type}', + existing, + record, ) elif not lenient and ( (record._type == 'CNAME' and len(node) > 0) @@ -142,9 +177,8 @@ class Zone(object): # We're adding a CNAME to existing records or adding to an existing # CNAME raise InvalidNodeException( - 'Invalid state, CNAME at ' - f'{record.fqdn} cannot coexist with ' - 'other records' + f'Invalid state, CNAME at {record.fqdn} cannot coexist with other records', + record, ) if record._type == 'NS' and record.name == '': diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index 1cf017a..af89f78 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -260,10 +260,13 @@ xn--dj-kia8a: zone = Zone('unit.tests.', ['sub']) with self.assertRaises(SubzoneRecordException) as ctx: source.populate(zone) - self.assertEqual( - 'Record www.sub.unit.tests. is under a managed subzone', - str(ctx.exception), + msg = str(ctx.exception) + self.assertTrue( + msg.startswith( + 'Record www.sub.unit.tests. is under a managed subzone' + ) ) + self.assertTrue(msg.endswith('unit.tests.yaml, line 201, column 3')) def test_SUPPORTS(self): source = YamlProvider('test', join(dirname(__file__), 'config')) @@ -536,10 +539,13 @@ class TestSplitYamlProvider(TestCase): zone = Zone('unit.tests.', ['sub']) with self.assertRaises(SubzoneRecordException) as ctx: source.populate(zone) - self.assertEqual( - 'Record www.sub.unit.tests. is under a managed subzone', - str(ctx.exception), + msg = str(ctx.exception) + self.assertTrue( + msg.startswith( + 'Record www.sub.unit.tests. is under a managed subzone' + ) ) + self.assertTrue(msg.endswith('www.sub.yaml, line 3, column 3')) def test_copy(self): # going to put some sentinal values in here to ensure, these aren't diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 29561c3..4aaa989 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -628,3 +628,13 @@ class TestRecordValidation(TestCase): ContextDict({'ttl': 42, 'value': '1.2.3.4'}, context='needle'), ) self.assertTrue('needle' in str(ctx.exception)) + + def test_context_copied_to_record(self): + record = Record.new( + self.zone, + 'www', + ContextDict( + {'ttl': 42, 'type': 'A', 'value': '1.2.3.4'}, context='needle' + ), + ) + self.assertEqual('needle', record.context) diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index 6563105..a01b6e9 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -6,6 +6,7 @@ from unittest import TestCase from helpers import SimpleProvider +from octodns.context import ContextDict from octodns.idna import idna_encode from octodns.record import ( AaaaRecord, @@ -106,6 +107,62 @@ class TestZone(TestCase): zone.add_record(b) self.assertEqual(zone.records, set([a, b])) + def test_duplicate_context_handling(self): + zone = Zone('unit.tests.', []) + + # these will be ==, but one has context and the other doesn't + no_context = ARecord(zone, 'a', {'ttl': 42, 'value': '1.1.1.1'}) + has_context = ARecord( + zone, 'a', {'ttl': 42, 'value': '1.1.1.1'}, context='hello world' + ) + + # both have context + zone.add_record(has_context) + with self.assertRaises(DuplicateRecordException) as ctx: + zone.add_record(has_context) + self.assertEqual(has_context, ctx.exception.existing) + self.assertEqual(has_context, ctx.exception.new) + zone.remove_record(has_context) + self.assertEqual( + [ + 'Duplicate record a.unit.tests., type A', + ' existing: hello world', + ' new: hello world', + ], + str(ctx.exception).split('\n'), + ) + + # new has context + zone.add_record(no_context) + with self.assertRaises(DuplicateRecordException) as ctx: + zone.add_record(has_context) + self.assertEqual(no_context, ctx.exception.existing) + self.assertEqual(has_context, ctx.exception.new) + zone.remove_record(no_context) + self.assertEqual( + [ + 'Duplicate record a.unit.tests., type A', + ' existing: [UNKNOWN]', + ' new: hello world', + ], + str(ctx.exception).split('\n'), + ) + + # existing has context + zone.add_record(has_context) + with self.assertRaises(DuplicateRecordException) as ctx: + zone.add_record(no_context) + self.assertEqual(has_context, ctx.exception.existing) + self.assertEqual(no_context, ctx.exception.new) + self.assertEqual( + [ + 'Duplicate record a.unit.tests., type A', + ' existing: hello world', + ' new: [UNKNOWN]', + ], + str(ctx.exception).split('\n'), + ) + def test_changes(self): before = Zone('unit.tests.', []) a = ARecord(before, 'a', {'ttl': 42, 'value': '1.1.1.1'}) @@ -242,9 +299,11 @@ class TestZone(TestCase): 'sub', {'ttl': 3600, 'type': 'A', 'values': ['1.2.3.4', '2.3.4.5']}, ) + record.context = 'added context' with self.assertRaises(SubzoneRecordException) as ctx: zone.add_record(record) self.assertTrue('not of type NS', str(ctx.exception)) + self.assertTrue(', added context' in str(ctx.exception)) # Can add it w/lenient zone.add_record(record, lenient=True) self.assertEqual(set([record]), zone.records) @@ -328,11 +387,13 @@ class TestZone(TestCase): cname = Record.new( zone, 'www', {'ttl': 60, 'type': 'CNAME', 'value': 'foo.bar.com.'} ) + cname.context = 'has some context' # add cname to a zone.add_record(a) - with self.assertRaises(InvalidNodeException): + with self.assertRaises(InvalidNodeException) as ctx: zone.add_record(cname) + self.assertTrue(', has some context' in str(ctx.exception)) self.assertEqual(set([a]), zone.records) zone.add_record(cname, lenient=True) self.assertEqual(set([a, cname]), zone.records) @@ -501,6 +562,22 @@ class TestZone(TestCase): # Doesn't the second self.assertFalse(copy.hydrate()) + def test_copy_context(self): + zone = Zone('unit.tests.', []) + + no_context = Record.new( + zone, 'a', {'ttl': 42, 'type': 'A', 'value': '1.1.1.1'} + ) + self.assertFalse(no_context.context) + self.assertFalse(no_context.copy().context) + + data = ContextDict( + {'ttl': 42, 'type': 'A', 'value': '1.1.1.1'}, context='hello world' + ) + has_context = Record.new(zone, 'a', data) + self.assertTrue(has_context.context) + self.assertTrue(has_context.copy().context) + def test_root_ns(self): zone = Zone('unit.tests.', []) From 3d719de1a52e28f8a4dd349073fde4a94eb75350 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 23 Aug 2023 13:28:26 -0700 Subject: [PATCH 2/6] Add octodns-spf to the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ab50fc..0c1823e 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ The table below lists the providers octoDNS supports. They are maintained in the | [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 | From 6f39fcc5f7f29d83c8cb6e59904829c89454e032 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sun, 27 Aug 2023 08:37:26 -0700 Subject: [PATCH 3/6] yaml provider is either split or zonefile, not both --- octodns/provider/yaml.py | 30 +++++-------------- tests/test_octodns_provider_yaml.py | 46 ++--------------------------- 2 files changed, 11 insertions(+), 65 deletions(-) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 1be77d2..93b7615 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -54,12 +54,8 @@ class YamlProvider(BaseProvider): # filenames or you would prefer to avoid them you can enable # split_catchall to instead write those records into a file named # `$[zone.name].yaml` - # (optional, default False) - split_catchall: false - - # Disable loading of the zone .yaml files. - # (optional, default False) - disable_zonefile: false + # (optional, default True) + split_catchall: true Split Details ------------- @@ -170,15 +166,14 @@ class YamlProvider(BaseProvider): populate_should_replace=False, supports_root_ns=True, split_extension=False, - split_catchall=False, - disable_zonefile=False, + split_catchall=True, *args, **kwargs, ): klass = self.__class__.__name__ self.log = logging.getLogger(f'{klass}[{id}]') self.log.debug( - '__init__: id=%s, directory=%s, default_ttl=%d, enforce_order=%d, populate_should_replace=%s, supports_root_ns=%s, split_extension=%s, split_catchall=%s, disable_zonefile=%s', + '__init__: id=%s, directory=%s, default_ttl=%d, enforce_order=%d, populate_should_replace=%s, supports_root_ns=%s, split_extension=%s, split_catchall=%s', id, directory, default_ttl, @@ -187,7 +182,6 @@ class YamlProvider(BaseProvider): supports_root_ns, split_extension, split_catchall, - disable_zonefile, ) super().__init__(id, *args, **kwargs) self.directory = directory @@ -197,7 +191,6 @@ class YamlProvider(BaseProvider): self.supports_root_ns = supports_root_ns self.split_extension = split_extension self.split_catchall = split_catchall - self.disable_zonefile = disable_zonefile def copy(self): kwargs = dict(self.__dict__) @@ -245,7 +238,7 @@ class YamlProvider(BaseProvider): dirname = dirname[:-trim] zones.add(dirname) - if not self.disable_zonefile: + if not self.split_extension: self.log.debug('list_zones: looking for zone files') for filename in listdir(self.directory): not_ends_with = not filename.endswith('.yaml') @@ -331,7 +324,7 @@ class YamlProvider(BaseProvider): if split_extension: sources.extend(self._split_sources(zone)) - if not self.disable_zonefile: + if not self.split_extension: sources.append(self._zone_sources(zone)) # determinstically order our sources @@ -431,20 +424,13 @@ class SplitYamlProvider(YamlProvider): # extension is configured as split_extension split_extension: . split_catchall: true - disable_zonefile: true TO BE REMOVED: 2.0 ''' def __init__(self, id, directory, *args, extension='.', **kwargs): - kwargs.update( - { - 'split_extension': extension, - 'split_catchall': True, - 'disable_zonefile': True, - } - ) + kwargs.update({'split_extension': extension, 'split_catchall': True}) super().__init__(id, directory, *args, **kwargs) self.log.warning( - '__init__: DEPRECATED use YamlProvider with split_extension, split_catchall, and disable_zonefile instead, will go away in v2.0' + '__init__: DEPRECATED use YamlProvider with split_extension and split_catchall instead, will go away in v2.0' ) diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index ae05b8f..98eca7f 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -345,50 +345,20 @@ xn--dj-kia8a: list(provider.list_zones()), ) - # include stuff with . AND basic + # split only . provider.split_extension = '.' self.assertEqual( - [ - 'both.tld.', - 'other.split.', - 'other.tld.', - 'split.test.', - 'sub.split.test.', - 'sub.unit.test.', - 'unit.test.', - ], - list(provider.list_zones()), - ) - - # include stuff with .tst AND basic - provider.split_extension = '.tst' - self.assertEqual( - [ - 'both.tld.', - 'other-ext.split.', - 'other.tld.', - 'split-ext.test.', - 'sub.split-ext.test.', - 'sub.unit.test.', - 'unit.test.', - ], + ['both.tld.', 'other.split.', 'split.test.', 'sub.split.test.'], list(provider.list_zones()), ) # only .tst - provider.disable_zonefile = True + provider.split_extension = '.tst' self.assertEqual( ['other-ext.split.', 'split-ext.test.', 'sub.split-ext.test.'], list(provider.list_zones()), ) - # only . (and both zone) - provider.split_extension = '.' - self.assertEqual( - ['both.tld.', 'other.split.', 'split.test.', 'sub.split.test.'], - list(provider.list_zones()), - ) - def test_split_sources(self): with TemporaryDirectory() as td: directory = join(td.dirname) @@ -501,16 +471,6 @@ class TestSplitYamlProvider(TestCase): self.assertEqual(20, len(zone.records)) self.assertFalse([r for r in zone.records if r.name.startswith('only')]) - # temporarily enable zone file processing too, we should see one extra - # record that came from unit.tests. - source.disable_zonefile = False - zone_both = Zone('unit.tests.', []) - source.populate(zone_both) - self.assertEqual(21, len(zone_both.records)) - n = len([r for r in zone_both.records if r.name == 'only-zone-file']) - self.assertEqual(1, n) - source.disable_zonefile = True - source.populate(dynamic_zone) self.assertEqual(5, len(dynamic_zone.records)) self.assertFalse( From 77b1d7a1bab91d06780e4509e5bf3470915b0f5e Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 29 Aug 2023 12:34:23 -0700 Subject: [PATCH 4/6] Correct split_catchall doc --- octodns/provider/yaml.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 93b7615..2d4ee81 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -49,11 +49,12 @@ class YamlProvider(BaseProvider): # When writing YAML records out to disk with split_extension enabled # each record is written out into its own file with .yaml appended to - # the name of the record. This would result in files like `.yaml` for - # the apex and `*.yaml` for a wildcard. If your OS doesn't allow such - # filenames or you would prefer to avoid them you can enable - # split_catchall to instead write those records into a file named - # `$[zone.name].yaml` + # the name of the record. The two exceptions are for the root and + # wildcard nodes. These records are written into a file named + # `$[zone.name].yaml`. If you would prefer this catchall file not be + # used `split_catchall` can be set to False to instead write those + # records out to `.yaml` and `*.yaml` respectively. Note that some + # operating systems may not allow files with those names. # (optional, default True) split_catchall: true From 8177ee6926648bb8dd69788374167b3bf97b4020 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 29 Aug 2023 12:59:38 -0700 Subject: [PATCH 5/6] Revert "yaml provider is either split or zonefile, not both" This reverts commit 6f39fcc5f7f29d83c8cb6e59904829c89454e032. --- octodns/provider/yaml.py | 24 +++++++++++---- tests/test_octodns_provider_yaml.py | 46 +++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 2d4ee81..d340bc6 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -58,6 +58,10 @@ class YamlProvider(BaseProvider): # (optional, default True) split_catchall: true + # Disable loading of the zone .yaml files. + # (optional, default False) + disable_zonefile: false + Split Details ------------- @@ -168,13 +172,14 @@ class YamlProvider(BaseProvider): supports_root_ns=True, split_extension=False, split_catchall=True, + disable_zonefile=False, *args, **kwargs, ): klass = self.__class__.__name__ self.log = logging.getLogger(f'{klass}[{id}]') self.log.debug( - '__init__: id=%s, directory=%s, default_ttl=%d, enforce_order=%d, populate_should_replace=%s, supports_root_ns=%s, split_extension=%s, split_catchall=%s', + '__init__: id=%s, directory=%s, default_ttl=%d, enforce_order=%d, populate_should_replace=%s, supports_root_ns=%s, split_extension=%s, split_catchall=%s, disable_zonefile=%s', id, directory, default_ttl, @@ -183,6 +188,7 @@ class YamlProvider(BaseProvider): supports_root_ns, split_extension, split_catchall, + disable_zonefile, ) super().__init__(id, *args, **kwargs) self.directory = directory @@ -192,6 +198,7 @@ class YamlProvider(BaseProvider): self.supports_root_ns = supports_root_ns self.split_extension = split_extension self.split_catchall = split_catchall + self.disable_zonefile = disable_zonefile def copy(self): kwargs = dict(self.__dict__) @@ -239,7 +246,7 @@ class YamlProvider(BaseProvider): dirname = dirname[:-trim] zones.add(dirname) - if not self.split_extension: + if not self.disable_zonefile: self.log.debug('list_zones: looking for zone files') for filename in listdir(self.directory): not_ends_with = not filename.endswith('.yaml') @@ -325,7 +332,7 @@ class YamlProvider(BaseProvider): if split_extension: sources.extend(self._split_sources(zone)) - if not self.split_extension: + if not self.disable_zonefile: sources.append(self._zone_sources(zone)) # determinstically order our sources @@ -425,13 +432,20 @@ class SplitYamlProvider(YamlProvider): # extension is configured as split_extension split_extension: . split_catchall: true + disable_zonefile: true TO BE REMOVED: 2.0 ''' def __init__(self, id, directory, *args, extension='.', **kwargs): - kwargs.update({'split_extension': extension, 'split_catchall': True}) + kwargs.update( + { + 'split_extension': extension, + 'split_catchall': True, + 'disable_zonefile': True, + } + ) super().__init__(id, directory, *args, **kwargs) self.log.warning( - '__init__: DEPRECATED use YamlProvider with split_extension and split_catchall instead, will go away in v2.0' + '__init__: DEPRECATED use YamlProvider with split_extension, split_catchall, and disable_zonefile instead, will go away in v2.0' ) diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index 814eb81..a41a417 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -348,20 +348,50 @@ xn--dj-kia8a: list(provider.list_zones()), ) - # split only . + # include stuff with . AND basic provider.split_extension = '.' self.assertEqual( - ['both.tld.', 'other.split.', 'split.test.', 'sub.split.test.'], + [ + 'both.tld.', + 'other.split.', + 'other.tld.', + 'split.test.', + 'sub.split.test.', + 'sub.unit.test.', + 'unit.test.', + ], + list(provider.list_zones()), + ) + + # include stuff with .tst AND basic + provider.split_extension = '.tst' + self.assertEqual( + [ + 'both.tld.', + 'other-ext.split.', + 'other.tld.', + 'split-ext.test.', + 'sub.split-ext.test.', + 'sub.unit.test.', + 'unit.test.', + ], list(provider.list_zones()), ) # only .tst - provider.split_extension = '.tst' + provider.disable_zonefile = True self.assertEqual( ['other-ext.split.', 'split-ext.test.', 'sub.split-ext.test.'], list(provider.list_zones()), ) + # only . (and both zone) + provider.split_extension = '.' + self.assertEqual( + ['both.tld.', 'other.split.', 'split.test.', 'sub.split.test.'], + list(provider.list_zones()), + ) + def test_split_sources(self): with TemporaryDirectory() as td: directory = join(td.dirname) @@ -474,6 +504,16 @@ class TestSplitYamlProvider(TestCase): self.assertEqual(20, len(zone.records)) self.assertFalse([r for r in zone.records if r.name.startswith('only')]) + # temporarily enable zone file processing too, we should see one extra + # record that came from unit.tests. + source.disable_zonefile = False + zone_both = Zone('unit.tests.', []) + source.populate(zone_both) + self.assertEqual(21, len(zone_both.records)) + n = len([r for r in zone_both.records if r.name == 'only-zone-file']) + self.assertEqual(1, n) + source.disable_zonefile = True + source.populate(dynamic_zone) self.assertEqual(5, len(dynamic_zone.records)) self.assertFalse( From 93b397cbb2f5170a4875dc7357f31404e8700423 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 29 Aug 2023 13:51:53 -0700 Subject: [PATCH 6/6] Correct Split Details directory example --- octodns/provider/yaml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index d340bc6..da6fdf1 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -76,7 +76,7 @@ class YamlProvider(BaseProvider): zones/ github.com./ - .yaml + $github.com.yaml www.yaml ...