From 72244ba16153099944f3b651fccfa7f79f3bf50c Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 27 Aug 2022 15:23:52 -0700 Subject: [PATCH 1/8] Provider._process_desired_zone should call .supports, rather than do in SUPPORTS --- octodns/provider/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octodns/provider/base.py b/octodns/provider/base.py index 78001fa..19a86dc 100644 --- a/octodns/provider/base.py +++ b/octodns/provider/base.py @@ -59,7 +59,7 @@ class BaseProvider(BaseSource): ''' for record in desired.records: - if record._type not in self.SUPPORTS: + if not self.supports(record): msg = f'{record._type} records not supported for {record.fqdn}' fallback = 'omitting record' self.supports_warn_or_except(msg, fallback) From 557b80f7847e14b179694af66c77afecb9cc74e0 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 27 Aug 2022 15:40:02 -0700 Subject: [PATCH 2/8] Implement YamlProvider.supports that always says yes --- octodns/provider/yaml.py | 8 ++++++++ tests/test_octodns_provider_yaml.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 9ad0934..ee436f2 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -167,6 +167,14 @@ class YamlProvider(BaseProvider): del args['log'] return self.__class__(**args) + def supports(self, record): + # We're overriding this as a performance tweak, namely to avoid calling + # the implementation of the SUPPORTS property to create a set from a + # dict_keys every single time something checked whether we support a + # record, the answer is always yes so that's overkill and we can just + # return True here and be done with it + return True + @property def SUPPORTS_ROOT_NS(self): return self.supports_root_ns diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index e2f55c2..99af97d 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -217,6 +217,20 @@ class TestYamlProvider(TestCase): str(ctx.exception), ) + def test_supports_everything(self): + source = YamlProvider('test', join(dirname(__file__), 'config')) + + class DummyType(object): + def __init__(self, _type): + self._type = _type + + # No matter what we check it's always supported + self.assertTrue(source.supports(DummyType(None))) + self.assertTrue(source.supports(DummyType(42))) + self.assertTrue(source.supports(DummyType('A'))) + self.assertTrue(source.supports(DummyType(source))) + self.assertTrue(source.supports(DummyType(self))) + class TestSplitYamlProvider(TestCase): def test_list_all_yaml_files(self): From 2e3d325f71285f9812a1ddcffae21b28b648a8a5 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 27 Aug 2022 15:53:56 -0700 Subject: [PATCH 3/8] YamlProvider.SUPPORTS dynamically returns the list of registered types --- octodns/provider/yaml.py | 28 ++++++++-------------------- octodns/record/__init__.py | 4 ++++ tests/test_octodns_provider_yaml.py | 20 ++++++++++++++++++-- tests/test_octodns_record.py | 4 ++++ 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index ee436f2..5044332 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -111,26 +111,6 @@ class YamlProvider(BaseProvider): SUPPORTS_DYNAMIC = True SUPPORTS_POOL_VALUE_STATUS = True SUPPORTS_MULTIVALUE_PTR = True - SUPPORTS = set( - ( - 'A', - 'AAAA', - 'ALIAS', - 'CAA', - 'CNAME', - 'DNAME', - 'LOC', - 'MX', - 'NAPTR', - 'NS', - 'PTR', - 'SSHFP', - 'SPF', - 'SRV', - 'TXT', - 'URLFWD', - ) - ) def __init__( self, @@ -167,6 +147,14 @@ class YamlProvider(BaseProvider): del args['log'] return self.__class__(**args) + @property + def SUPPORTS(self): + # The yaml provider supports all record types even those defined by 3rd + # party modules that we know nothing about, thus we dynamically return + # the types list that is registered in Record, everything that's know as + # of the point in time we're asked + return set(Record.registered_types().keys()) + def supports(self, record): # We're overriding this as a performance tweak, namely to avoid calling # the implementation of the SUPPORTS property to create a set from a diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index d7c6be4..6cb6bf1 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -102,6 +102,10 @@ class Record(EqualityTupleMixin): raise RecordException(msg) cls._CLASSES[_type] = _class + @classmethod + def registered_types(cls): + return cls._CLASSES + @classmethod def new(cls, zone, name, data, source=None, lenient=False): name = str(name).lower() diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index 99af97d..811aae4 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -15,7 +15,7 @@ from unittest import TestCase from yaml import safe_load from yaml.constructor import ConstructorError -from octodns.record import Create +from octodns.record import _NsValue, Create, Record, ValuesMixin from octodns.provider.base import Plan from octodns.provider.yaml import ( _list_all_yaml_files, @@ -217,7 +217,23 @@ class TestYamlProvider(TestCase): str(ctx.exception), ) - def test_supports_everything(self): + def test_SUPPORTS(self): + source = YamlProvider('test', join(dirname(__file__), 'config')) + # make sure the provider supports all the registered types + self.assertEqual(Record.registered_types().keys(), source.SUPPORTS) + + class YamlRecord(ValuesMixin, Record): + _type = 'YAML' + _value_type = _NsValue + + # don't know anything about a yaml type + self.assertTrue('YAML' not in source.SUPPORTS) + # register it + Record.register_type(YamlRecord) + # when asked again we'll now include it in our list of supports + self.assertTrue('YAML' in source.SUPPORTS) + + def test_supports(self): source = YamlProvider('test', join(dirname(__file__), 'config')) class DummyType(object): diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 2bbea5d..626f325 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -69,6 +69,8 @@ class TestRecord(TestCase): _type = 'AA' _value_type = _NsValue + self.assertTrue('AA' not in Record.registered_types()) + Record.register_type(AaRecord) aa = Record.new( self.zone, @@ -77,6 +79,8 @@ class TestRecord(TestCase): ) self.assertEqual(AaRecord, aa.__class__) + self.assertTrue('AA' in Record.registered_types()) + def test_lowering(self): record = ARecord( self.zone, 'MiXeDcAsE', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} From b664a28488dcec4a7daa5fe671de6d913945ca92 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 27 Aug 2022 15:54:48 -0700 Subject: [PATCH 4/8] Changelog update about Provider._process_desired_zone and YamlProvider.supports --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f36874..78685f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## v0.9.19 - 2022-??-?? - ??? + +* Addressed shortcomings with YamlProvider.SUPPORTS in that it didn't include + dynamically registered types, was a static list that could have drifted over + time even ignoring 3rd party types. +* Provider._process_desired_zone needed to call Provider.supports rather than + doing it's own `_type in provider.SUPPORTS`. The default behavior in + Source.supports is ^, but it's possible for providers to override that + behavior and do special checking and `_process_desired_zone` wasn't taking + that into account. +* Now that it's used as it needed to be YamlProvider overrides + Provider.supports and just always says Yes so that any dynamically registered + types will be supported. + ## v0.9.18 - 2022-08-14 - Subzone handling * Fixed issue with sub-zone handling introduced in 0.9.18 From fa4225b625654c51c7b0be6efcfd6a1109768a72 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 6 Sep 2022 12:47:47 -0700 Subject: [PATCH 5/8] Fix lots of errant '...' '...' string joins --- octodns/cmds/dump.py | 4 +-- octodns/cmds/sync.py | 2 +- octodns/manager.py | 20 +++++++-------- octodns/provider/base.py | 7 ++---- octodns/provider/plan.py | 2 +- octodns/record/__init__.py | 6 ++--- octodns/record/geo_data.py | 2 +- octodns/source/base.py | 8 +++--- octodns/source/envvar.py | 2 +- octodns/source/tinydns.py | 2 +- octodns/zone.py | 10 ++++---- tests/test_octodns_manager.py | 2 +- tests/test_octodns_provider_yaml.py | 4 +-- tests/test_octodns_record.py | 38 ++++++++++++++--------------- 14 files changed, 52 insertions(+), 57 deletions(-) diff --git a/octodns/cmds/dump.py b/octodns/cmds/dump.py index 8d30913..5629342 100755 --- a/octodns/cmds/dump.py +++ b/octodns/cmds/dump.py @@ -38,13 +38,13 @@ def main(): '--lenient', action='store_true', default=False, - help='Ignore record validations and do a best effort ' 'dump', + help='Ignore record validations and do a best effort dump', ) parser.add_argument( '--split', action='store_true', default=False, - help='Split the dumped zone into a YAML file per ' 'record', + help='Split the dumped zone into a YAML file per record', ) parser.add_argument('zone', help='Zone to dump') parser.add_argument('source', nargs='+', help='Source(s) to pull data from') diff --git a/octodns/cmds/sync.py b/octodns/cmds/sync.py index fcaa5ea..05bc146 100755 --- a/octodns/cmds/sync.py +++ b/octodns/cmds/sync.py @@ -26,7 +26,7 @@ def main(): '--doit', action='store_true', default=False, - help='Whether to take action or just show what would ' 'change', + help='Whether to take action or just show what would change', ) parser.add_argument( '--force', diff --git a/octodns/manager.py b/octodns/manager.py index 40a9c4f..19c5122 100644 --- a/octodns/manager.py +++ b/octodns/manager.py @@ -141,7 +141,7 @@ class Manager(object): except KeyError: self.log.exception('Invalid provider class') raise ManagerException( - f'Provider {provider_name} is missing ' 'class' + f'Provider {provider_name} is missing class' ) _class, module, version = self._get_named_class('provider', _class) kwargs = self._build_kwargs(provider_config) @@ -168,7 +168,7 @@ class Manager(object): except KeyError: self.log.exception('Invalid processor class') raise ManagerException( - f'Processor {processor_name} is ' 'missing class' + f'Processor {processor_name} is missing class' ) _class, module, version = self._get_named_class('processor', _class) kwargs = self._build_kwargs(processor_config) @@ -204,7 +204,7 @@ class Manager(object): except KeyError: self.log.exception('Invalid plan_output class') raise ManagerException( - f'plan_output {plan_output_name} is ' 'missing class' + f'plan_output {plan_output_name} is missing class' ) _class, module, version = self._get_named_class( 'plan_output', _class @@ -264,7 +264,7 @@ class Manager(object): module, version = self._import_module(module_name) except (ImportError, ValueError): self.log.exception( - '_get_{}_class: Unable to import ' 'module %s', _class + '_get_{}_class: Unable to import module %s', _class ) raise ManagerException(f'Unknown {_type} class: {_class}') @@ -272,7 +272,7 @@ class Manager(object): return getattr(module, class_name), module_name, version except AttributeError: self.log.exception( - '_get_{}_class: Unable to get class %s ' 'from module %s', + '_get_{}_class: Unable to get class %s from module %s', class_name, module, ) @@ -360,7 +360,7 @@ class Manager(object): if "unexpected keyword argument 'lenient'" not in str(e): raise self.log.warning( - 'provider %s does not accept lenient ' 'param', + 'provider %s does not accept lenient param', source.__class__.__name__, ) source.populate(zone) @@ -389,7 +389,7 @@ class Manager(object): if "keyword argument 'processors'" not in str(e): raise self.log.warning( - 'provider.plan %s does not accept processors ' 'param', + 'provider.plan %s does not accept processors param', target.__class__.__name__, ) plan = target.plan(zone) @@ -522,7 +522,7 @@ class Manager(object): trg = self.providers[target] if not isinstance(trg, BaseProvider): raise ManagerException( - f'{trg} - "{target}" does not ' 'support targeting' + f'{trg} - "{target}" does not support targeting' ) trgs.append(trg) targets = trgs @@ -696,7 +696,7 @@ class Manager(object): raise ManagerException(msg) target = target.copy() self.log.info( - 'dump: setting directory of output_provider ' 'copy to %s', + 'dump: setting directory of output_provider copy to %s', output_dir, ) target.directory = output_dir @@ -781,7 +781,7 @@ class Manager(object): def get_zone(self, zone_name): if not zone_name[-1] == '.': raise ManagerException( - f'Invalid zone name {zone_name}, missing ' 'ending dot' + f'Invalid zone name {zone_name}, missing ending dot' ) for name, config in self.config['zones'].items(): diff --git a/octodns/provider/base.py b/octodns/provider/base.py index 19a86dc..88f37c9 100644 --- a/octodns/provider/base.py +++ b/octodns/provider/base.py @@ -187,8 +187,7 @@ class BaseProvider(BaseSource): # If your code gets this warning see Source.populate for more # information self.log.warning( - 'Provider %s used in target mode did not return ' 'exists', - self.id, + 'Provider %s used in target mode did not return exists', self.id ) # Make a (shallow) copy of the desired state so that everything from @@ -254,6 +253,4 @@ class BaseProvider(BaseSource): return len(plan.changes) def _apply(self, plan): - raise NotImplementedError( - 'Abstract base class, _apply method ' 'missing' - ) + raise NotImplementedError('Abstract base class, _apply method missing') diff --git a/octodns/provider/plan.py b/octodns/provider/plan.py index a436483..b562579 100644 --- a/octodns/provider/plan.py +++ b/octodns/provider/plan.py @@ -73,7 +73,7 @@ class Plan(object): existing_n = 0 self.log.debug( - '__init__: Creates=%d, Updates=%d, Deletes=%d ' 'Existing=%d', + '__init__: Creates=%d, Updates=%d, Deletes=%d Existing=%d', self.change_counts['Create'], self.change_counts['Update'], self.change_counts['Delete'], diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index 6cb6bf1..851574d 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -624,7 +624,7 @@ class _DynamicMixin(object): if len(values) == 1 and values[0].get('weight', 1) != 1: reasons.append( - f'pool "{_id}" has single value with ' 'weight!=1' + f'pool "{_id}" has single value with weight!=1' ) fallback = pool.get('fallback', None) @@ -1307,7 +1307,7 @@ class _NsValue(object): for value in data: if not FQDN(str(value), allow_underscores=True).is_valid: reasons.append( - f'Invalid NS value "{value}" is not ' 'a valid FQDN.' + f'Invalid NS value "{value}" is not a valid FQDN.' ) elif not value.endswith('.'): reasons.append(f'NS value "{value}" missing trailing .') @@ -1519,7 +1519,7 @@ class SrvValue(EqualityTupleMixin): and not FQDN(str(target), allow_underscores=True).is_valid ): reasons.append( - f'Invalid SRV target "{target}" is not ' 'a valid FQDN.' + f'Invalid SRV target "{target}" is not a valid FQDN.' ) except KeyError: reasons.append('missing target') diff --git a/octodns/record/geo_data.py b/octodns/record/geo_data.py index baee616..0b0407c 100644 --- a/octodns/record/geo_data.py +++ b/octodns/record/geo_data.py @@ -288,7 +288,7 @@ geo_data = { 'SD': {'name': 'South Dakota'}, 'TN': {'name': 'Tennessee'}, 'TX': {'name': 'Texas'}, - 'UM': {'name': 'United States Minor Outlying ' 'Islands'}, + 'UM': {'name': 'United States Minor Outlying Islands'}, 'UT': {'name': 'Utah'}, 'VA': {'name': 'Virginia'}, 'VI': {'name': 'Virgin Islands'}, diff --git a/octodns/source/base.py b/octodns/source/base.py index fdd2bbc..f7a2925 100644 --- a/octodns/source/base.py +++ b/octodns/source/base.py @@ -20,15 +20,15 @@ class BaseSource(object): self.id = id if not getattr(self, 'log', False): raise NotImplementedError( - 'Abstract base class, log property ' 'missing' + 'Abstract base class, log property missing' ) if not hasattr(self, 'SUPPORTS_GEO'): raise NotImplementedError( - 'Abstract base class, SUPPORTS_GEO ' 'property missing' + 'Abstract base class, SUPPORTS_GEO property missing' ) if not hasattr(self, 'SUPPORTS'): raise NotImplementedError( - 'Abstract base class, SUPPORTS ' 'property missing' + 'Abstract base class, SUPPORTS property missing' ) @property @@ -51,7 +51,7 @@ class BaseSource(object): True if the zone exists or False if it does not. ''' raise NotImplementedError( - 'Abstract base class, populate method ' 'missing' + 'Abstract base class, populate method missing' ) def supports(self, record): diff --git a/octodns/source/envvar.py b/octodns/source/envvar.py index 95c5ffc..d13c33b 100644 --- a/octodns/source/envvar.py +++ b/octodns/source/envvar.py @@ -67,7 +67,7 @@ class EnvVarSource(BaseSource): klass = self.__class__.__name__ self.log = logging.getLogger(f'{klass}[{id}]') self.log.debug( - '__init__: id=%s, variable=%s, name=%s, ' 'ttl=%d', + '__init__: id=%s, variable=%s, name=%s, ttl=%d', id, variable, name, diff --git a/octodns/source/tinydns.py b/octodns/source/tinydns.py index 19bf734..19cf9a5 100755 --- a/octodns/source/tinydns.py +++ b/octodns/source/tinydns.py @@ -174,7 +174,7 @@ class TinyDnsBaseSource(BaseSource): zone.add_record(record, lenient=lenient) except SubzoneRecordException: self.log.debug( - '_populate_normal: skipping subzone ' 'record=%s', + '_populate_normal: skipping subzone record=%s', record, ) diff --git a/octodns/zone.py b/octodns/zone.py index 1bc3724..4c818e8 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -146,7 +146,7 @@ class Zone(object): continue elif len(record.included) > 0 and target.id not in record.included: self.log.debug( - 'changes: skipping record=%s %s - %s not' ' included ', + 'changes: skipping record=%s %s - %s not included ', record.fqdn, record._type, target.id, @@ -154,7 +154,7 @@ class Zone(object): continue elif target.id in record.excluded: self.log.debug( - 'changes: skipping record=%s %s - %s ' 'excluded ', + 'changes: skipping record=%s %s - %s excluded ', record.fqdn, record._type, target.id, @@ -169,7 +169,7 @@ class Zone(object): and target.id not in desired_record.included ): self.log.debug( - 'changes: skipping record=%s %s - %s' 'not included ', + 'changes: skipping record=%s %s - %s not included', record.fqdn, record._type, target.id, @@ -216,7 +216,7 @@ class Zone(object): continue elif len(record.included) > 0 and target.id not in record.included: self.log.debug( - 'changes: skipping record=%s %s - %s not' ' included ', + 'changes: skipping record=%s %s - %s not included ', record.fqdn, record._type, target.id, @@ -224,7 +224,7 @@ class Zone(object): continue elif target.id in record.excluded: self.log.debug( - 'changes: skipping record=%s %s - %s ' 'excluded ', + 'changes: skipping record=%s %s - %s excluded ', record.fqdn, record._type, target.id, diff --git a/tests/test_octodns_manager.py b/tests/test_octodns_manager.py index bdacb08..7ea9202 100644 --- a/tests/test_octodns_manager.py +++ b/tests/test_octodns_manager.py @@ -441,7 +441,7 @@ class TestManager(TestCase): sources=['in'], ) self.assertEqual( - 'output_provider=simple, does not support ' 'copy method', + 'output_provider=simple, does not support copy method', str(ctx.exception), ) diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index 811aae4..92939aa 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -213,7 +213,7 @@ class TestYamlProvider(TestCase): with self.assertRaises(SubzoneRecordException) as ctx: source.populate(zone) self.assertEqual( - 'Record www.sub.unit.tests. is under a managed ' 'subzone', + 'Record www.sub.unit.tests. is under a managed subzone', str(ctx.exception), ) @@ -470,7 +470,7 @@ class TestSplitYamlProvider(TestCase): with self.assertRaises(SubzoneRecordException) as ctx: source.populate(zone) self.assertEqual( - 'Record www.sub.unit.tests. is under a managed ' 'subzone', + 'Record www.sub.unit.tests. is under a managed subzone', str(ctx.exception), ) diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 626f325..7e41a48 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -61,7 +61,7 @@ class TestRecord(TestCase): with self.assertRaises(RecordException) as ctx: Record.register_type(None, 'A') self.assertEqual( - 'Type "A" already registered by ' 'octodns.record.ARecord', + 'Type "A" already registered by octodns.record.ARecord', str(ctx.exception), ) @@ -1859,7 +1859,7 @@ class TestRecordValidation(TestCase): self.assertTrue(reason.startswith('invalid fqdn, "xxxx')) self.assertTrue( reason.endswith( - '.unit.tests." is too long at 254' ' chars, max is 253' + '.unit.tests." is too long at 254 chars, max is 253' ) ) @@ -1873,7 +1873,7 @@ class TestRecordValidation(TestCase): reason = ctx.exception.reasons[0] self.assertTrue(reason.startswith('invalid label, "xxxx')) self.assertTrue( - reason.endswith('xxx" is too long at 64' ' chars, max is 63') + reason.endswith('xxx" is too long at 64 chars, max is 63') ) with self.assertRaises(ValidationError) as ctx: @@ -1884,7 +1884,7 @@ class TestRecordValidation(TestCase): reason = ctx.exception.reasons[0] self.assertTrue(reason.startswith('invalid label, "xxxx')) self.assertTrue( - reason.endswith('xxx" is too long at 64' ' chars, max is 63') + reason.endswith('xxx" is too long at 64 chars, max is 63') ) # should not raise with dots @@ -2472,7 +2472,7 @@ class TestRecordValidation(TestCase): {'type': 'CNAME', 'ttl': 600, 'value': 'https://google.com'}, ) self.assertEqual( - ['CNAME value "https://google.com" is not a valid ' 'FQDN'], + ['CNAME value "https://google.com" is not a valid FQDN'], ctx.exception.reasons, ) @@ -2488,7 +2488,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - ['CNAME value "https://google.com/a/b/c" is not a ' 'valid FQDN'], + ['CNAME value "https://google.com/a/b/c" is not a valid FQDN'], ctx.exception.reasons, ) @@ -2500,7 +2500,7 @@ class TestRecordValidation(TestCase): {'type': 'CNAME', 'ttl': 600, 'value': 'google.com/some/path'}, ) self.assertEqual( - ['CNAME value "google.com/some/path" is not a valid ' 'FQDN'], + ['CNAME value "google.com/some/path" is not a valid FQDN'], ctx.exception.reasons, ) @@ -2971,7 +2971,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - ['Invalid MX exchange "100 foo.bar.com." is not a ' 'valid FQDN.'], + ['Invalid MX exchange "100 foo.bar.com." is not a valid FQDN.'], ctx.exception.reasons, ) @@ -3082,7 +3082,7 @@ class TestRecordValidation(TestCase): {'type': 'NS', 'ttl': 600, 'value': '100 foo.bar.com.'}, ) self.assertEqual( - ['Invalid NS value "100 foo.bar.com." is not a ' 'valid FQDN.'], + ['Invalid NS value "100 foo.bar.com." is not a valid FQDN.'], ctx.exception.reasons, ) @@ -3283,7 +3283,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - ['unescaped ; in "this has some; ' 'semi-colons\\; in it"'], + ['unescaped ; in "this has some; semi-colons\\; in it"'], ctx.exception.reasons, ) @@ -3487,7 +3487,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - ['Invalid SRV target "100 foo.bar.com." is not a ' 'valid FQDN.'], + ['Invalid SRV target "100 foo.bar.com." is not a valid FQDN.'], ctx.exception.reasons, ) @@ -3589,7 +3589,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - 'invalid certificate_usage ' '"{value["certificate_usage"]}"', + 'invalid certificate_usage "{value["certificate_usage"]}"', ctx.exception.reasons, ) @@ -3610,7 +3610,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - 'invalid certificate_usage ' '"{value["certificate_usage"]}"', + 'invalid certificate_usage "{value["certificate_usage"]}"', ctx.exception.reasons, ) @@ -3648,8 +3648,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - 'invalid selector ' '"{value["selector"]}"', - ctx.exception.reasons, + 'invalid selector "{value["selector"]}"', ctx.exception.reasons ) # Invalid selector @@ -3669,8 +3668,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - 'invalid selector ' '"{value["selector"]}"', - ctx.exception.reasons, + 'invalid selector "{value["selector"]}"', ctx.exception.reasons ) # missing matching_type @@ -3707,7 +3705,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - 'invalid matching_type ' '"{value["matching_type"]}"', + 'invalid matching_type "{value["matching_type"]}"', ctx.exception.reasons, ) @@ -3728,7 +3726,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - 'invalid matching_type ' '"{value["matching_type"]}"', + 'invalid matching_type "{value["matching_type"]}"', ctx.exception.reasons, ) @@ -3765,7 +3763,7 @@ class TestRecordValidation(TestCase): }, ) self.assertEqual( - ['unescaped ; in "this has some; semi-colons\\; ' 'in it"'], + ['unescaped ; in "this has some; semi-colons\\; in it"'], ctx.exception.reasons, ) From d54baf0ea660e0a007d8fca3cd4cb6cda246824e Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 6 Sep 2022 12:48:42 -0700 Subject: [PATCH 6/8] Ignore the string joins ref --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 8dc0e50..6bca725 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,4 @@ # Commit that added in black formatting support e116d26eeca0891c31b689e43db5bb60b62f73f6 +# Commit that fixed a bunch of uneeded '...' '...' string joins from ^ +fa4225b625654c51c7b0be6efcfd6a1109768a72 From 51f8eb17f0b647eba1a9dee7a71545ac93f2b706 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 6 Sep 2022 12:49:37 -0700 Subject: [PATCH 7/8] Update to latest best practice for ignore-revs and hooks --- script/bootstrap | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/script/bootstrap b/script/bootstrap index 543d7b7..a7d85e7 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -29,8 +29,17 @@ if [ "$ENV" != "production" ]; then python -m pip install -r requirements-dev.txt fi -if [ ! -L ".git/hooks/pre-commit" ]; then - ln -s "$ROOT/.git_hooks_pre-commit" ".git/hooks/pre-commit" +if [ -d ".git" ]; then + if [ -f ".git-blame-ignore-revs" ]; then + echo "" + echo "Setting blame.ignoreRevsFile to .git-blame-ingore-revs" + git config --local blame.ignoreRevsFile .git-blame-ignore-revs + fi + if [ ! -L ".git/hooks/pre-commit" ]; then + echo "" + echo "Installing pre-commit hook" + ln -s "$ROOT/.git_hooks_pre-commit" ".git/hooks/pre-commit" + fi fi echo "" From c4d9362664dc12ec25f66f87de275389f32ad1c5 Mon Sep 17 00:00:00 2001 From: Lukasz Polanski Date: Sat, 10 Sep 2022 23:23:54 +0200 Subject: [PATCH 8/8] Update README.md Adding links to the noteworthy NetBox-DNS plugin and its OctoDNS provider --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 666e4b9..788619f 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,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. - [`kompetenzbolzen/octodns-custom-provider`](https://github.com/kompetenzbolzen/octodns-custom-provider): zonefile provider & phpIPAM source. - **Resources.** - Article: [Visualising DNS records with Neo4j](https://medium.com/@costask/querying-and-visualising-octodns-records-with-neo4j-f4f72ab2d474) + code