From 66debc0b806de823e5fa2fdb2640a5ccf248050c Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 15 Sep 2022 14:25:47 -0700 Subject: [PATCH 1/4] Use super() now that we require python3, less error prone --- octodns/cmds/args.py | 4 +-- octodns/cmds/report.py | 6 ++-- octodns/processor/acme.py | 2 +- octodns/processor/ownership.py | 2 +- octodns/provider/base.py | 2 +- octodns/provider/plan.py | 2 +- octodns/provider/yaml.py | 4 +-- octodns/record/__init__.py | 50 ++++++++++++++--------------- octodns/source/axfr.py | 14 ++++---- octodns/source/envvar.py | 6 ++-- octodns/source/tinydns.py | 4 +-- tests/helpers.py | 4 +-- tests/test_octodns_provider_base.py | 4 +-- 13 files changed, 49 insertions(+), 55 deletions(-) diff --git a/octodns/cmds/args.py b/octodns/cmds/args.py index ac00079..170c852 100644 --- a/octodns/cmds/args.py +++ b/octodns/cmds/args.py @@ -18,7 +18,7 @@ class ArgumentParser(_Base): ''' def __init__(self, *args, **kwargs): - super(ArgumentParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def parse_args(self, default_log_level=INFO): version = f'octoDNS {__VERSION__}' @@ -50,7 +50,7 @@ class ArgumentParser(_Base): '--debug', action='store_true', default=False, help=_help ) - args = super(ArgumentParser, self).parse_args() + args = super().parse_args() self._setup_logging(args, default_log_level) return args diff --git a/octodns/cmds/report.py b/octodns/cmds/report.py index 2bc5fa3..88bf71f 100755 --- a/octodns/cmds/report.py +++ b/octodns/cmds/report.py @@ -16,13 +16,11 @@ from octodns.manager import Manager class AsyncResolver(Resolver): def __init__(self, num_workers, *args, **kwargs): - super(AsyncResolver, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.executor = ThreadPoolExecutor(max_workers=num_workers) def query(self, *args, **kwargs): - return self.executor.submit( - super(AsyncResolver, self).query, *args, **kwargs - ) + return self.executor.submit(super().query, *args, **kwargs) def main(): diff --git a/octodns/processor/acme.py b/octodns/processor/acme.py index cab3f16..793f95a 100644 --- a/octodns/processor/acme.py +++ b/octodns/processor/acme.py @@ -25,7 +25,7 @@ class AcmeMangingProcessor(BaseProcessor): - acme ... ''' - super(AcmeMangingProcessor, self).__init__(name) + super().__init__(name) self._owned = set() diff --git a/octodns/processor/ownership.py b/octodns/processor/ownership.py index 083a583..1abbbea 100644 --- a/octodns/processor/ownership.py +++ b/octodns/processor/ownership.py @@ -15,7 +15,7 @@ from .base import BaseProcessor # and thus "own" them going forward. class OwnershipProcessor(BaseProcessor): def __init__(self, name, txt_name='_owner', txt_value='*octodns*'): - super(OwnershipProcessor, self).__init__(name) + super().__init__(name) self.txt_name = txt_name self.txt_value = txt_value self._txt_values = [txt_value] diff --git a/octodns/provider/base.py b/octodns/provider/base.py index ae9c018..65e156b 100644 --- a/octodns/provider/base.py +++ b/octodns/provider/base.py @@ -17,7 +17,7 @@ class BaseProvider(BaseSource): delete_pcent_threshold=Plan.MAX_SAFE_DELETE_PCENT, strict_supports=False, ): - super(BaseProvider, self).__init__(id) + super().__init__(id) self.log.debug( '__init__: id=%s, apply_disabled=%s, ' 'update_pcent_threshold=%.2f, ' diff --git a/octodns/provider/plan.py b/octodns/provider/plan.py index ceacb25..5e38749 100644 --- a/octodns/provider/plan.py +++ b/octodns/provider/plan.py @@ -136,7 +136,7 @@ class _PlanOutput(object): class PlanLogger(_PlanOutput): def __init__(self, name, level='info'): - super(PlanLogger, self).__init__(name) + super().__init__(name) try: self.level = { 'debug': DEBUG, diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 3a3252b..c2e8b3f 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -128,7 +128,7 @@ class YamlProvider(BaseProvider): enforce_order, populate_should_replace, ) - super(YamlProvider, self).__init__(id, *args, **kwargs) + super().__init__(id, *args, **kwargs) self.directory = directory self.default_ttl = default_ttl self.enforce_order = enforce_order @@ -311,7 +311,7 @@ class SplitYamlProvider(YamlProvider): CATCHALL_RECORD_NAMES = ('*', '') def __init__(self, id, directory, extension='.', *args, **kwargs): - super(SplitYamlProvider, self).__init__(id, directory, *args, **kwargs) + super().__init__(id, directory, *args, **kwargs) self.extension = extension def _zone_directory(self, zone): diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index b3663fd..c4a4422 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -31,7 +31,7 @@ class Create(Change): CLASS_ORDERING = 1 def __init__(self, new): - super(Create, self).__init__(None, new) + super().__init__(None, new) def __repr__(self, leader=''): source = self.new.source.id if self.new.source else '' @@ -57,7 +57,7 @@ class Delete(Change): CLASS_ORDERING = 0 def __init__(self, existing): - super(Delete, self).__init__(existing, None) + super().__init__(existing, None) def __repr__(self, leader=''): return f'Delete {self.existing}' @@ -74,7 +74,7 @@ class ValidationError(RecordException): return f'Invalid record {idna_decode(fqdn)}\n - {reasons}' def __init__(self, fqdn, reasons): - super(Exception, self).__init__(self.build_message(fqdn, reasons)) + super().__init__(self.build_message(fqdn, reasons)) self.fqdn = fqdn self.reasons = reasons @@ -329,7 +329,7 @@ class GeoValue(EqualityTupleMixin): class ValuesMixin(object): @classmethod def validate(cls, name, fqdn, data): - reasons = super(ValuesMixin, cls).validate(name, fqdn, data) + reasons = super().validate(name, fqdn, data) values = data.get('values', data.get('value', [])) @@ -338,7 +338,7 @@ class ValuesMixin(object): return reasons def __init__(self, zone, name, data, source=None): - super(ValuesMixin, self).__init__(zone, name, data, source=source) + super().__init__(zone, name, data, source=source) try: values = data['values'] except KeyError: @@ -348,10 +348,10 @@ class ValuesMixin(object): def changes(self, other, target): if self.values != other.values: return Update(self, other) - return super(ValuesMixin, self).changes(other, target) + return super().changes(other, target) def _data(self): - ret = super(ValuesMixin, self)._data() + ret = super()._data() if len(self.values) > 1: values = [getattr(v, 'data', v) for v in self.values if v] if len(values) > 1: @@ -380,7 +380,7 @@ class _GeoMixin(ValuesMixin): @classmethod def validate(cls, name, fqdn, data): - reasons = super(_GeoMixin, cls).validate(name, fqdn, data) + reasons = super().validate(name, fqdn, data) try: geo = dict(data['geo']) for code, values in geo.items(): @@ -391,7 +391,7 @@ class _GeoMixin(ValuesMixin): return reasons def __init__(self, zone, name, data, *args, **kwargs): - super(_GeoMixin, self).__init__(zone, name, data, *args, **kwargs) + super().__init__(zone, name, data, *args, **kwargs) try: self.geo = dict(data['geo']) except KeyError: @@ -400,7 +400,7 @@ class _GeoMixin(ValuesMixin): self.geo[code] = GeoValue(code, values) def _data(self): - ret = super(_GeoMixin, self)._data() + ret = super()._data() if self.geo: geo = {} for code, value in self.geo.items(): @@ -412,7 +412,7 @@ class _GeoMixin(ValuesMixin): if target.SUPPORTS_GEO: if self.geo != other.geo: return Update(self, other) - return super(_GeoMixin, self).changes(other, target) + return super().changes(other, target) def __repr__(self): if self.geo: @@ -421,29 +421,29 @@ class _GeoMixin(ValuesMixin): f'<{klass} {self._type} {self.ttl}, {self.decoded_fqdn}, ' f'{self.values}, {self.geo}>' ) - return super(_GeoMixin, self).__repr__() + return super().__repr__() class ValueMixin(object): @classmethod def validate(cls, name, fqdn, data): - reasons = super(ValueMixin, cls).validate(name, fqdn, data) + reasons = super().validate(name, fqdn, data) reasons.extend( cls._value_type.validate(data.get('value', None), cls._type) ) return reasons def __init__(self, zone, name, data, source=None): - super(ValueMixin, self).__init__(zone, name, data, source=source) + super().__init__(zone, name, data, source=source) self.value = self._value_type.process(data['value']) def changes(self, other, target): if self.value != other.value: return Update(self, other) - return super(ValueMixin, self).changes(other, target) + return super().changes(other, target) def _data(self): - ret = super(ValueMixin, self)._data() + ret = super()._data() if self.value: ret['value'] = getattr(self.value, 'data', self.value) return ret @@ -565,7 +565,7 @@ class _DynamicMixin(object): @classmethod def validate(cls, name, fqdn, data): - reasons = super(_DynamicMixin, cls).validate(name, fqdn, data) + reasons = super().validate(name, fqdn, data) if 'dynamic' not in data: return reasons @@ -724,7 +724,7 @@ class _DynamicMixin(object): return reasons def __init__(self, zone, name, data, *args, **kwargs): - super(_DynamicMixin, self).__init__(zone, name, data, *args, **kwargs) + super().__init__(zone, name, data, *args, **kwargs) self.dynamic = {} @@ -754,7 +754,7 @@ class _DynamicMixin(object): self.dynamic = _Dynamic(pools, parsed) def _data(self): - ret = super(_DynamicMixin, self)._data() + ret = super()._data() if self.dynamic: ret['dynamic'] = self.dynamic._data() return ret @@ -763,7 +763,7 @@ class _DynamicMixin(object): if target.SUPPORTS_DYNAMIC: if self.dynamic != other.dynamic: return Update(self, other) - return super(_DynamicMixin, self).changes(other, target) + return super().changes(other, target) def __repr__(self): # TODO: improve this whole thing, we need multi-line... @@ -781,7 +781,7 @@ class _DynamicMixin(object): f'<{klass} {self._type} {self.ttl}, {self.decoded_fqdn}, ' f'{values}, {self.dynamic}>' ) - return super(_DynamicMixin, self).__repr__() + return super().__repr__() class _IpList(object): @@ -885,7 +885,7 @@ class AliasRecord(ValueMixin, Record): reasons = [] if name != '': reasons.append('non-root ALIAS not allowed') - reasons.extend(super(AliasRecord, cls).validate(name, fqdn, data)) + reasons.extend(super().validate(name, fqdn, data)) return reasons @@ -951,7 +951,7 @@ class CnameRecord(_DynamicMixin, ValueMixin, Record): reasons = [] if name == '': reasons.append('root CNAME not allowed') - reasons.extend(super(CnameRecord, cls).validate(name, fqdn, data)) + reasons.extend(super().validate(name, fqdn, data)) return reasons @@ -1352,7 +1352,7 @@ class PtrValue(_TargetValue): reasons.append('missing values') for value in values: - reasons.extend(super(PtrValue, cls).validate(value, _type)) + reasons.extend(super().validate(value, _type)) return reasons @@ -1578,7 +1578,7 @@ class SrvRecord(ValuesMixin, Record): reasons = [] if not cls._name_re.match(name): reasons.append('invalid name for SRV record') - reasons.extend(super(SrvRecord, cls).validate(name, fqdn, data)) + reasons.extend(super().validate(name, fqdn, data)) return reasons diff --git a/octodns/source/axfr.py b/octodns/source/axfr.py index 164a466..e40d7a9 100644 --- a/octodns/source/axfr.py +++ b/octodns/source/axfr.py @@ -40,7 +40,7 @@ class AxfrBaseSource(BaseSource): ) def __init__(self, id): - super(AxfrBaseSource, self).__init__(id) + super().__init__(id) def _data_for_multiple(self, _type, records): return { @@ -186,9 +186,7 @@ class AxfrSourceException(Exception): class AxfrSourceZoneTransferFailed(AxfrSourceException): def __init__(self): - super(AxfrSourceZoneTransferFailed, self).__init__( - 'Unable to Perform Zone Transfer' - ) + super().__init__('Unable to Perform Zone Transfer') class AxfrSource(AxfrBaseSource): @@ -204,7 +202,7 @@ class AxfrSource(AxfrBaseSource): def __init__(self, id, master): self.log = logging.getLogger(f'AxfrSource[{id}]') self.log.debug('__init__: id=%s, master=%s', id, master) - super(AxfrSource, self).__init__(id) + super().__init__(id) self.master = master def zone_records(self, zone): @@ -238,12 +236,12 @@ class ZoneFileSourceException(Exception): class ZoneFileSourceNotFound(ZoneFileSourceException): def __init__(self): - super(ZoneFileSourceNotFound, self).__init__('Zone file not found') + super().__init__('Zone file not found') class ZoneFileSourceLoadFailure(ZoneFileSourceException): def __init__(self, error): - super(ZoneFileSourceLoadFailure, self).__init__(str(error)) + super().__init__(str(error)) class ZoneFileSource(AxfrBaseSource): @@ -275,7 +273,7 @@ class ZoneFileSource(AxfrBaseSource): file_extension, check_origin, ) - super(ZoneFileSource, self).__init__(id) + super().__init__(id) self.directory = directory self.file_extension = file_extension self.check_origin = check_origin diff --git a/octodns/source/envvar.py b/octodns/source/envvar.py index d13c33b..6ca80df 100644 --- a/octodns/source/envvar.py +++ b/octodns/source/envvar.py @@ -11,9 +11,7 @@ class EnvVarSourceException(Exception): class EnvironmentVariableNotFoundException(EnvVarSourceException): def __init__(self, data): - super(EnvironmentVariableNotFoundException, self).__init__( - f'Unknown environment variable {data}' - ) + super().__init__(f'Unknown environment variable {data}') class EnvVarSource(BaseSource): @@ -73,7 +71,7 @@ class EnvVarSource(BaseSource): name, ttl, ) - super(EnvVarSource, self).__init__(id) + super().__init__(id) self.envvar = variable self.name = name self.ttl = ttl diff --git a/octodns/source/tinydns.py b/octodns/source/tinydns.py index 30189f7..20a03ea 100755 --- a/octodns/source/tinydns.py +++ b/octodns/source/tinydns.py @@ -23,7 +23,7 @@ class TinyDnsBaseSource(BaseSource): split_re = re.compile(r':+') def __init__(self, id, default_ttl=3600): - super(TinyDnsBaseSource, self).__init__(id) + super().__init__(id) self.default_ttl = default_ttl def _data_for_A(self, _type, records): @@ -239,7 +239,7 @@ class TinyDnsFileSource(TinyDnsBaseSource): directory, default_ttl, ) - super(TinyDnsFileSource, self).__init__(id, default_ttl) + super().__init__(id, default_ttl) self.directory = directory self._cache = None diff --git a/tests/helpers.py b/tests/helpers.py index 6efd604..ecdc2cc 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -95,7 +95,7 @@ class TemporaryDirectory(object): class WantsConfigProcessor(BaseProcessor): def __init__(self, name, some_config): - super(WantsConfigProcessor, self).__init__(name) + super().__init__(name) class PlannableProvider(BaseProvider): @@ -106,7 +106,7 @@ class PlannableProvider(BaseProvider): SUPPORTS = set(('A', 'AAAA', 'TXT')) def __init__(self, *args, **kwargs): - super(PlannableProvider, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def populate(self, zone, source=False, target=False, lenient=False): pass diff --git a/tests/test_octodns_provider_base.py b/tests/test_octodns_provider_base.py index 0a78ff7..ab3b945 100644 --- a/tests/test_octodns_provider_base.py +++ b/tests/test_octodns_provider_base.py @@ -53,7 +53,7 @@ class HelperProvider(BaseProvider): class TrickyProcessor(BaseProcessor): def __init__(self, name, add_during_process_target_zone): - super(TrickyProcessor, self).__init__(name) + super().__init__(name) self.add_during_process_target_zone = add_during_process_target_zone self.reset() @@ -640,7 +640,7 @@ class TestBaseProvider(TestCase): def __init__(self, **kwargs): self.log = MagicMock() - super(MinimalProvider, self).__init__('minimal', **kwargs) + super().__init__('minimal', **kwargs) normal = MinimalProvider(strict_supports=False) # Should log and not expect From 3b102b451613e4b8144ce625ff4419c1633e3251 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Thu, 15 Sep 2022 19:26:16 -0700 Subject: [PATCH 2/4] hostname_from_fqdn work with utf8 or idna, whichevr it's passed --- octodns/zone.py | 11 +++++++++-- tests/test_octodns_zone.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/octodns/zone.py b/octodns/zone.py index f0df959..ff8252c 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -41,7 +41,8 @@ class Zone(object): self._root_ns = None # optional leading . to match empty hostname # optional trailing . b/c some sources don't have it on their fqdn - self._name_re = re.compile(fr'\.?{name}?$') + self._utf8_name_re = re.compile(fr'\.?{idna_decode(name)}?$') + self._idna_name_re = re.compile(fr'\.?{self.name}?$') # Copy-on-write semantics support, when `not None` this property will # point to a location with records for this `Zone`. Once `hydrated` @@ -63,7 +64,13 @@ class Zone(object): return self._root_ns def hostname_from_fqdn(self, fqdn): - return self._name_re.sub('', fqdn) + try: + fqdn.encode('ascii') + # it's non-idna or idna encoded + return self._idna_name_re.sub('', idna_encode(fqdn)) + except UnicodeEncodeError: + # it has utf8 chars + return self._utf8_name_re.sub('', fqdn) def add_record(self, record, replace=False, lenient=False): if self._origin: diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index 708862f..86fd689 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -47,10 +47,16 @@ class TestZone(TestCase): ('foo.bar', 'foo.bar.unit.tests'), ('foo.unit.tests', 'foo.unit.tests.unit.tests.'), ('foo.unit.tests', 'foo.unit.tests.unit.tests'), + # if we pass utf8 we get utf8 ('déjà', 'déjà.unit.tests'), ('déjà.foo', 'déjà.foo.unit.tests'), ('bar.déjà', 'bar.déjà.unit.tests'), ('bar.déjà.foo', 'bar.déjà.foo.unit.tests'), + # if we pass idna we get idna + ('xn--dj-kia8a', 'xn--dj-kia8a.unit.tests'), + ('xn--dj-kia8a.foo', 'xn--dj-kia8a.foo.unit.tests'), + ('bar.xn--dj-kia8a', 'bar.xn--dj-kia8a.unit.tests'), + ('bar.xn--dj-kia8a.foo', 'bar.xn--dj-kia8a.foo.unit.tests'), ): self.assertEqual(hostname, zone.hostname_from_fqdn(fqdn)) @@ -68,6 +74,10 @@ class TestZone(TestCase): ('déjà.foo', 'déjà.foo.grüßen.de'), ('bar.déjà', 'bar.déjà.grüßen.de'), ('bar.déjà.foo', 'bar.déjà.foo.grüßen.de'), + ('xn--dj-kia8a', 'xn--dj-kia8a.xn--gren-wna7o.de'), + ('xn--dj-kia8a.foo', 'xn--dj-kia8a.foo.xn--gren-wna7o.de'), + ('bar.xn--dj-kia8a', 'bar.xn--dj-kia8a.xn--gren-wna7o.de'), + ('bar.xn--dj-kia8a.foo', 'bar.xn--dj-kia8a.foo.xn--gren-wna7o.de'), ): self.assertEqual(hostname, zone.hostname_from_fqdn(fqdn)) From 0af455049ae96f8faba2043d121d256f26bdfb4d Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 27 Sep 2022 07:18:17 -0700 Subject: [PATCH 3/4] Remove master mentions from README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 788619f..1bbad7e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -OctoDNS Logo +OctoDNS Logo ## DNS as code - Tools for managing DNS across multiple providers @@ -178,7 +178,7 @@ After the reviews it's time to branch deploy the change. ![Output of a deployment command](/docs/assets/deploy.png) -If that goes smoothly, you again see the expected changes, and verify them with `dig` and/or `octodns-report` you're good to hit the merge button. If there are problems you can quickly do a `.deploy dns/master` to go back to the previous state. +If that goes smoothly, you again see the expected changes, and verify them with `dig` and/or `octodns-report` you're good to hit the merge button. If there are problems you can quickly do a `.deploy dns/main` to go back to the previous state. ### Bootstrapping config files @@ -378,7 +378,7 @@ 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/master/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: 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. From 2a3eb2475969c1abf3c2c2e9968aa595759af5e7 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Tue, 27 Sep 2022 07:18:47 -0700 Subject: [PATCH 4/4] Remove stray pprint --- tests/test_octodns_manager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_octodns_manager.py b/tests/test_octodns_manager.py index 1a36723..abcf9a7 100644 --- a/tests/test_octodns_manager.py +++ b/tests/test_octodns_manager.py @@ -186,9 +186,6 @@ class TestManager(TestCase): manager.config['zones'] = manager._config_zones( {'déjà.vu.': {}, 'deja.vu.': {}, idna_encode('こんにちは.jp.'): {}} ) - from pprint import pprint - - pprint(manager.config['zones']) # refer to them with utf-8 with self.assertRaises(ManagerException) as ctx: