From e116d26eeca0891c31b689e43db5bb60b62f73f6 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 4 Jul 2022 12:27:39 -0700 Subject: [PATCH] Implement black formatting --- .git_hooks_pre-commit | 1 + octodns/__init__.py | 8 +- octodns/cmds/__init__.py | 8 +- octodns/cmds/args.py | 57 +- octodns/cmds/compare.py | 47 +- octodns/cmds/dump.py | 50 +- octodns/cmds/report.py | 50 +- octodns/cmds/sync.py | 72 +- octodns/cmds/validate.py | 15 +- octodns/cmds/versions.py | 15 +- octodns/equality.py | 9 +- octodns/manager.py | 355 +- octodns/processor/__init__.py | 8 +- octodns/processor/acme.py | 23 +- octodns/processor/awsacm.py | 27 +- octodns/processor/base.py | 9 +- octodns/processor/filter.py | 10 +- octodns/processor/ownership.py | 44 +- octodns/provider/__init__.py | 8 +- octodns/provider/azuredns.py | 29 +- octodns/provider/base.py | 108 +- octodns/provider/cloudflare.py | 31 +- octodns/provider/constellix.py | 31 +- octodns/provider/digitalocean.py | 31 +- octodns/provider/dnsimple.py | 29 +- octodns/provider/dnsmadeeasy.py | 31 +- octodns/provider/dyn.py | 29 +- octodns/provider/easydns.py | 29 +- octodns/provider/edgedns.py | 29 +- octodns/provider/etc_hosts.py | 29 +- octodns/provider/fastdns.py | 20 +- octodns/provider/gandi.py | 29 +- octodns/provider/gcore.py | 29 +- octodns/provider/googlecloud.py | 31 +- octodns/provider/hetzner.py | 29 +- octodns/provider/mythicbeasts.py | 31 +- octodns/provider/ns1.py | 29 +- octodns/provider/ovh.py | 29 +- octodns/provider/plan.py | 125 +- octodns/provider/powerdns.py | 29 +- octodns/provider/rackspace.py | 31 +- octodns/provider/route53.py | 29 +- octodns/provider/selectel.py | 29 +- octodns/provider/transip.py | 29 +- octodns/provider/ultra.py | 29 +- octodns/provider/yaml.py | 109 +- octodns/record/__init__.py | 390 +- octodns/record/geo.py | 17 +- octodns/record/geo_data.py | 664 ++- octodns/source/__init__.py | 8 +- octodns/source/axfr.py | 250 +- octodns/source/base.py | 28 +- octodns/source/envvar.py | 40 +- octodns/source/tinydns.py | 92 +- octodns/yaml.py | 25 +- octodns/zone.py | 137 +- requirements-dev.txt | 7 +- script/cibuild | 2 + script/format | 9 + script/lint | 1 - setup.py | 26 +- tests/helpers.py | 12 +- tests/test_octodns_equality.py | 12 +- tests/test_octodns_manager.py | 331 +- tests/test_octodns_plan.py | 170 +- tests/test_octodns_processor_acme.py | 103 +- tests/test_octodns_processor_awsacm.py | 10 +- tests/test_octodns_processor_filter.py | 70 +- tests/test_octodns_processor_ownership.py | 89 +- tests/test_octodns_provider_azuredns.py | 10 +- tests/test_octodns_provider_base.py | 535 +- tests/test_octodns_provider_cloudflare.py | 10 +- tests/test_octodns_provider_constellix.py | 10 +- tests/test_octodns_provider_digitalocean.py | 10 +- tests/test_octodns_provider_dnsimple.py | 10 +- tests/test_octodns_provider_dnsmadeeasy.py | 10 +- tests/test_octodns_provider_dyn.py | 10 +- tests/test_octodns_provider_easydns.py | 10 +- tests/test_octodns_provider_edgedns.py | 11 +- tests/test_octodns_provider_etc_hosts.py | 10 +- tests/test_octodns_provider_gandi.py | 11 +- tests/test_octodns_provider_gcore.py | 10 +- tests/test_octodns_provider_googlecloud.py | 10 +- tests/test_octodns_provider_hetzner.py | 10 +- tests/test_octodns_provider_mythicbeasts.py | 10 +- tests/test_octodns_provider_ns1.py | 10 +- tests/test_octodns_provider_ovh.py | 10 +- tests/test_octodns_provider_powerdns.py | 10 +- tests/test_octodns_provider_rackspace.py | 10 +- tests/test_octodns_provider_route53.py | 10 +- tests/test_octodns_provider_selectel.py | 10 +- tests/test_octodns_provider_transip.py | 10 +- tests/test_octodns_provider_ultra.py | 10 +- tests/test_octodns_provider_yaml.py | 189 +- tests/test_octodns_record.py | 5908 +++++++++---------- tests/test_octodns_record_geo.py | 107 +- tests/test_octodns_source_axfr.py | 48 +- tests/test_octodns_source_envvar.py | 1 - tests/test_octodns_source_tinydns.py | 251 +- tests/test_octodns_yaml.py | 66 +- tests/test_octodns_zone.py | 204 +- 101 files changed, 6403 insertions(+), 5490 deletions(-) create mode 100755 script/format diff --git a/.git_hooks_pre-commit b/.git_hooks_pre-commit index 1fb621f..7938eee 100755 --- a/.git_hooks_pre-commit +++ b/.git_hooks_pre-commit @@ -8,4 +8,5 @@ ROOT=$(dirname "$GIT") . "$ROOT/env/bin/activate" "$ROOT/script/lint" +"$ROOT/script/format" --check --quiet || (echo "Formatting check failed, run ./script/format" && exit 1) "$ROOT/script/coverage" diff --git a/octodns/__init__.py b/octodns/__init__.py index 1870f70..9d59853 100644 --- a/octodns/__init__.py +++ b/octodns/__init__.py @@ -1,6 +1,10 @@ 'OctoDNS: DNS as code - Tools for managing DNS across multiple providers' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) __VERSION__ = '0.9.17' diff --git a/octodns/cmds/__init__.py b/octodns/cmds/__init__.py index 14ccf18..16a8eb0 100644 --- a/octodns/cmds/__init__.py +++ b/octodns/cmds/__init__.py @@ -2,5 +2,9 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) diff --git a/octodns/cmds/args.py b/octodns/cmds/args.py index 41906ba..7f04f43 100644 --- a/octodns/cmds/args.py +++ b/octodns/cmds/args.py @@ -2,12 +2,15 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from argparse import ArgumentParser as _Base -from logging import DEBUG, INFO, WARN, Formatter, StreamHandler, \ - getLogger +from logging import DEBUG, INFO, WARN, Formatter, StreamHandler, getLogger from logging.handlers import SysLogHandler from sys import stderr, stdout @@ -26,22 +29,33 @@ class ArgumentParser(_Base): def parse_args(self, default_log_level=INFO): version = f'octoDNS {__VERSION__}' - self.add_argument('--version', action='version', version=version, - help='Print octoDNS version and exit') - self.add_argument('--log-stream-stdout', action='store_true', - default=False, - help='Log to stdout instead of stderr') + self.add_argument( + '--version', + action='version', + version=version, + help='Print octoDNS version and exit', + ) + self.add_argument( + '--log-stream-stdout', + action='store_true', + default=False, + help='Log to stdout instead of stderr', + ) _help = 'Send logging data to syslog in addition to stderr' - self.add_argument('--log-syslog', action='store_true', default=False, - help=_help) - self.add_argument('--syslog-device', default='/dev/log', - help='Syslog device') - self.add_argument('--syslog-facility', default='local0', - help='Syslog facility') + self.add_argument( + '--log-syslog', action='store_true', default=False, help=_help + ) + self.add_argument( + '--syslog-device', default='/dev/log', help='Syslog device' + ) + self.add_argument( + '--syslog-facility', default='local0', help='Syslog facility' + ) _help = 'Increase verbosity to get details and help track down issues' - self.add_argument('--debug', action='store_true', default=False, - help=_help) + self.add_argument( + '--debug', action='store_true', default=False, help=_help + ) args = super(ArgumentParser, self).parse_args() self._setup_logging(args, default_log_level) @@ -57,10 +71,13 @@ class ArgumentParser(_Base): logger.addHandler(handler) if args.log_syslog: - fmt = 'octodns[%(process)-5s:%(thread)d]: %(name)s ' \ + fmt = ( + 'octodns[%(process)-5s:%(thread)d]: %(name)s ' '%(levelname)-5s %(message)s' - handler = SysLogHandler(address=args.syslog_device, - facility=args.syslog_facility) + ) + handler = SysLogHandler( + address=args.syslog_device, facility=args.syslog_facility + ) handler.setFormatter(Formatter(fmt=fmt)) logger.addHandler(handler) diff --git a/octodns/cmds/compare.py b/octodns/cmds/compare.py index 9bf9f1c..818a436 100755 --- a/octodns/cmds/compare.py +++ b/octodns/cmds/compare.py @@ -3,8 +3,12 @@ Octo-DNS Comparator ''' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from pprint import pprint import sys @@ -16,16 +20,32 @@ from octodns.manager import Manager def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) - parser.add_argument('--config-file', required=True, - help='The Manager configuration file to use') - parser.add_argument('--a', nargs='+', required=True, - help='First source(s) to pull data from') - parser.add_argument('--b', nargs='+', required=True, - help='Second source(s) to pull data from') - parser.add_argument('--zone', default=None, required=True, - help='Zone to compare') - parser.add_argument('--ignore-prefix', default=None, required=False, - help='Record prefix to ignore from list of changes') + parser.add_argument( + '--config-file', + required=True, + help='The Manager configuration file to use', + ) + parser.add_argument( + '--a', + nargs='+', + required=True, + help='First source(s) to pull data from', + ) + parser.add_argument( + '--b', + nargs='+', + required=True, + help='Second source(s) to pull data from', + ) + parser.add_argument( + '--zone', default=None, required=True, help='Zone to compare' + ) + parser.add_argument( + '--ignore-prefix', + default=None, + required=False, + help='Record prefix to ignore from list of changes', + ) args = parser.parse_args() manager = Manager(args.config_file) @@ -34,8 +54,7 @@ def main(): # Filter changes list based on ignore-prefix argument if present if args.ignore_prefix: pattern = args.ignore_prefix - changes = [c for c in changes - if not c.record.fqdn.startswith(pattern)] + changes = [c for c in changes if not c.record.fqdn.startswith(pattern)] pprint(changes) diff --git a/octodns/cmds/dump.py b/octodns/cmds/dump.py index 9927468..41396a0 100755 --- a/octodns/cmds/dump.py +++ b/octodns/cmds/dump.py @@ -3,8 +3,12 @@ Octo-DNS Dumper ''' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from octodns.cmds.args import ArgumentParser from octodns.manager import Manager @@ -13,26 +17,38 @@ from octodns.manager import Manager def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) - parser.add_argument('--config-file', required=True, - help='The Manager configuration file to use') - parser.add_argument('--output-dir', required=True, - help='The directory into which the results will be ' - 'written (Note: will overwrite existing files)') - parser.add_argument('--lenient', action='store_true', default=False, - 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') + parser.add_argument( + '--config-file', + required=True, + help='The Manager configuration file to use', + ) + parser.add_argument( + '--output-dir', + required=True, + help='The directory into which the results will be ' + 'written (Note: will overwrite existing files)', + ) + parser.add_argument( + '--lenient', + action='store_true', + default=False, + 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', + ) parser.add_argument('zone', help='Zone to dump') - parser.add_argument('source', nargs='+', - help='Source(s) to pull data from') + parser.add_argument('source', nargs='+', help='Source(s) to pull data from') args = parser.parse_args() manager = Manager(args.config_file) - manager.dump(args.zone, args.output_dir, args.lenient, args.split, - *args.source) + manager.dump( + args.zone, args.output_dir, args.lenient, args.split, *args.source + ) if __name__ == '__main__': diff --git a/octodns/cmds/report.py b/octodns/cmds/report.py index c733492..d13e85c 100755 --- a/octodns/cmds/report.py +++ b/octodns/cmds/report.py @@ -3,8 +3,12 @@ Octo-DNS Reporter ''' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from concurrent.futures import ThreadPoolExecutor from dns.exception import Timeout @@ -18,28 +22,38 @@ from octodns.manager import Manager class AsyncResolver(Resolver): - def __init__(self, num_workers, *args, **kwargs): super(AsyncResolver, self).__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(AsyncResolver, self).query, *args, **kwargs + ) def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) - parser.add_argument('--config-file', required=True, - help='The Manager configuration file to use') + parser.add_argument( + '--config-file', + required=True, + help='The Manager configuration file to use', + ) parser.add_argument('--zone', required=True, help='Zone to dump') - parser.add_argument('--source', required=True, default=[], action='append', - help='Source(s) to pull data from') - parser.add_argument('--num-workers', default=4, - help='Number of background workers') - parser.add_argument('--timeout', default=1, - help='Number seconds to wait for an answer') + parser.add_argument( + '--source', + required=True, + default=[], + action='append', + help='Source(s) to pull data from', + ) + parser.add_argument( + '--num-workers', default=4, help='Number of background workers' + ) + parser.add_argument( + '--timeout', default=1, help='Number seconds to wait for an answer' + ) parser.add_argument('server', nargs='+', help='Servers to query') args = parser.parse_args() @@ -62,8 +76,9 @@ def main(): resolvers = [] ip_addr_re = re.compile(r'^[\d\.]+$') for server in args.server: - resolver = AsyncResolver(configure=False, - num_workers=int(args.num_workers)) + resolver = AsyncResolver( + configure=False, num_workers=int(args.num_workers) + ) if not ip_addr_re.match(server): server = str(query(server, 'A')[0]) log.info('server=%s', server) @@ -73,8 +88,9 @@ def main(): queries = {} for record in sorted(zone.records): - queries[record] = [r.query(record.fqdn, record._type) - for r in resolvers] + queries[record] = [ + r.query(record.fqdn, record._type) for r in resolvers + ] for record, futures in sorted(queries.items(), key=lambda d: d[0]): stdout.write(record.fqdn) diff --git a/octodns/cmds/sync.py b/octodns/cmds/sync.py index dbf4103..fcaa5ea 100755 --- a/octodns/cmds/sync.py +++ b/octodns/cmds/sync.py @@ -3,8 +3,12 @@ Octo-DNS Multiplexer ''' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from octodns.cmds.args import ArgumentParser from octodns.manager import Manager @@ -13,31 +17,57 @@ from octodns.manager import Manager def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) - parser.add_argument('--config-file', required=True, - help='The Manager configuration file to use') - parser.add_argument('--doit', action='store_true', default=False, - help='Whether to take action or just show what would ' - 'change') - parser.add_argument('--force', action='store_true', default=False, - help='Acknowledge that significant changes are being ' - 'made and do them') + parser.add_argument( + '--config-file', + required=True, + help='The Manager configuration file to use', + ) + parser.add_argument( + '--doit', + action='store_true', + default=False, + help='Whether to take action or just show what would ' 'change', + ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help='Acknowledge that significant changes are being ' + 'made and do them', + ) - parser.add_argument('zone', nargs='*', default=[], - help='Limit sync to the specified zone(s)') + parser.add_argument( + 'zone', + nargs='*', + default=[], + help='Limit sync to the specified zone(s)', + ) - parser.add_argument('--source', default=[], action='append', - help='Limit sync to zones with the specified ' - 'source(s) (all sources will be synchronized for the ' - 'selected zones)') - parser.add_argument('--target', default=[], action='append', - help='Limit sync to the specified target(s)') + parser.add_argument( + '--source', + default=[], + action='append', + help='Limit sync to zones with the specified ' + 'source(s) (all sources will be synchronized for the ' + 'selected zones)', + ) + parser.add_argument( + '--target', + default=[], + action='append', + help='Limit sync to the specified target(s)', + ) args = parser.parse_args() manager = Manager(args.config_file) - manager.sync(eligible_zones=args.zone, eligible_sources=args.source, - eligible_targets=args.target, dry_run=not args.doit, - force=args.force) + manager.sync( + eligible_zones=args.zone, + eligible_sources=args.source, + eligible_targets=args.target, + dry_run=not args.doit, + force=args.force, + ) if __name__ == '__main__': diff --git a/octodns/cmds/validate.py b/octodns/cmds/validate.py index 6711ec9..f69c02b 100755 --- a/octodns/cmds/validate.py +++ b/octodns/cmds/validate.py @@ -3,8 +3,12 @@ Octo-DNS Validator ''' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import WARN @@ -15,8 +19,11 @@ from octodns.manager import Manager def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) - parser.add_argument('--config-file', required=True, - help='The Manager configuration file to use') + parser.add_argument( + '--config-file', + required=True, + help='The Manager configuration file to use', + ) args = parser.parse_args(WARN) diff --git a/octodns/cmds/versions.py b/octodns/cmds/versions.py index 7cb3a45..2794ee4 100755 --- a/octodns/cmds/versions.py +++ b/octodns/cmds/versions.py @@ -3,8 +3,12 @@ octoDNS Versions ''' -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from octodns.cmds.args import ArgumentParser from octodns.manager import Manager @@ -13,8 +17,11 @@ from octodns.manager import Manager def main(): parser = ArgumentParser(description=__doc__.split('\n')[1]) - parser.add_argument('--config-file', required=True, - help='The Manager configuration file to use') + parser.add_argument( + '--config-file', + required=True, + help='The Manager configuration file to use', + ) args = parser.parse_args() diff --git a/octodns/equality.py b/octodns/equality.py index bd22c7d..db08a2a 100644 --- a/octodns/equality.py +++ b/octodns/equality.py @@ -2,12 +2,15 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) class EqualityTupleMixin(object): - def _equality_tuple(self): raise NotImplementedError('_equality_tuple method not implemented') diff --git a/octodns/manager.py b/octodns/manager.py index 030298e..d68e817 100644 --- a/octodns/manager.py +++ b/octodns/manager.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from concurrent.futures import ThreadPoolExecutor from importlib import import_module @@ -22,9 +26,12 @@ from .zone import Zone # TODO: this can go away once we no longer support python 3.7 try: - from importlib.metadata import PackageNotFoundError, \ - version as module_version + from importlib.metadata import ( + PackageNotFoundError, + version as module_version, + ) except ModuleNotFoundError: # pragma: no cover + class PackageNotFoundError(Exception): pass @@ -61,7 +68,6 @@ class _AggregateTarget(object): class MakeThreadFuture(object): - def __init__(self, func, args, kwargs): self.func = func self.args = args @@ -98,24 +104,29 @@ class Manager(object): def __init__(self, config_file, max_workers=None, include_meta=False): version = self._try_version('octodns', version=__VERSION__) - self.log.info('__init__: config_file=%s (octoDNS %s)', config_file, - version) + self.log.info( + '__init__: config_file=%s (octoDNS %s)', config_file, version + ) # Read our config file with open(config_file, 'r') as fh: self.config = safe_load(fh, enforce_order=False) manager_config = self.config.get('manager', {}) - max_workers = manager_config.get('max_workers', 1) \ - if max_workers is None else max_workers + max_workers = ( + manager_config.get('max_workers', 1) + if max_workers is None + else max_workers + ) self.log.info('__init__: max_workers=%d', max_workers) if max_workers > 1: self._executor = ThreadPoolExecutor(max_workers=max_workers) else: self._executor = MainThreadExecutor() - self.include_meta = include_meta or manager_config.get('include_meta', - False) + self.include_meta = include_meta or manager_config.get( + 'include_meta', False + ) self.log.info('__init__: include_meta=%s', self.include_meta) self.log.debug('__init__: configuring providers') @@ -126,87 +137,112 @@ class Manager(object): _class = provider_config.pop('class') except KeyError: self.log.exception('Invalid provider class') - raise ManagerException(f'Provider {provider_name} is missing ' - 'class') + raise ManagerException( + f'Provider {provider_name} is missing ' 'class' + ) _class, module, version = self._get_named_class('provider', _class) kwargs = self._build_kwargs(provider_config) try: self.providers[provider_name] = _class(provider_name, **kwargs) - self.log.info('__init__: provider=%s (%s %s)', provider_name, - module, version) + self.log.info( + '__init__: provider=%s (%s %s)', + provider_name, + module, + version, + ) except TypeError: self.log.exception('Invalid provider config') - raise ManagerException('Incorrect provider config for ' + - provider_name) + raise ManagerException( + 'Incorrect provider config for ' + provider_name + ) self.processors = {} - for processor_name, processor_config in \ - self.config.get('processors', {}).items(): + for processor_name, processor_config in self.config.get( + 'processors', {} + ).items(): try: _class = processor_config.pop('class') except KeyError: self.log.exception('Invalid processor class') - raise ManagerException(f'Processor {processor_name} is ' - 'missing class') - _class, module, version = self._get_named_class('processor', - _class) + raise ManagerException( + f'Processor {processor_name} is ' 'missing class' + ) + _class, module, version = self._get_named_class('processor', _class) kwargs = self._build_kwargs(processor_config) try: - self.processors[processor_name] = _class(processor_name, - **kwargs) - self.log.info('__init__: processor=%s (%s %s)', processor_name, - module, version) + self.processors[processor_name] = _class( + processor_name, **kwargs + ) + self.log.info( + '__init__: processor=%s (%s %s)', + processor_name, + module, + version, + ) except TypeError: self.log.exception('Invalid processor config') - raise ManagerException('Incorrect processor config for ' + - processor_name) + raise ManagerException( + 'Incorrect processor config for ' + processor_name + ) zone_tree = {} # Sort so we iterate on the deepest nodes first, ensuring if a parent # zone exists it will be seen after the subzone, thus we can easily # reparent children to their parent zone from the tree root. - for name in sorted(self.config['zones'].keys(), - key=lambda s: 0 - s.count('.')): + for name in sorted( + self.config['zones'].keys(), key=lambda s: 0 - s.count('.') + ): # Trim the trailing dot from FQDN name = name[:-1] this = {} for sz in [k for k in zone_tree.keys() if k.endswith(name)]: # Found a zone in tree root that is our child, slice the # name and move its tree under ours. - this[sz[:-(len(name) + 1)]] = zone_tree.pop(sz) + this[sz[: -(len(name) + 1)]] = zone_tree.pop(sz) # Add to tree root where it will be reparented as we iterate up # the tree. zone_tree[name] = this self.zone_tree = zone_tree self.plan_outputs = {} - plan_outputs = manager_config.get('plan_outputs', { - '_logger': { - 'class': 'octodns.provider.plan.PlanLogger', - 'level': 'info' - } - }) + plan_outputs = manager_config.get( + 'plan_outputs', + { + '_logger': { + 'class': 'octodns.provider.plan.PlanLogger', + 'level': 'info', + } + }, + ) for plan_output_name, plan_output_config in plan_outputs.items(): try: _class = plan_output_config.pop('class') except KeyError: self.log.exception('Invalid plan_output class') - raise ManagerException(f'plan_output {plan_output_name} is ' - 'missing class') - _class, module, version = self._get_named_class('plan_output', - _class) + raise ManagerException( + f'plan_output {plan_output_name} is ' 'missing class' + ) + _class, module, version = self._get_named_class( + 'plan_output', _class + ) kwargs = self._build_kwargs(plan_output_config) try: - self.plan_outputs[plan_output_name] = \ - _class(plan_output_name, **kwargs) + self.plan_outputs[plan_output_name] = _class( + plan_output_name, **kwargs + ) # Don't print out version info for the default output if plan_output_name != '_logger': - self.log.info('__init__: plan_output=%s (%s %s)', - plan_output_name, module, version) + self.log.info( + '__init__: plan_output=%s (%s %s)', + plan_output_name, + module, + version, + ) except TypeError: self.log.exception('Invalid plan_output config') - raise ManagerException('Incorrect plan_output config for ' + - plan_output_name) + raise ManagerException( + 'Incorrect plan_output config for ' + plan_output_name + ) def _try_version(self, module_name, module=None, version=None): try: @@ -241,15 +277,19 @@ class Manager(object): module_name, class_name = _class.rsplit('.', 1) module, version = self._import_module(module_name) except (ImportError, ValueError): - self.log.exception('_get_{}_class: Unable to import ' - 'module %s', _class) + self.log.exception( + '_get_{}_class: Unable to import ' 'module %s', _class + ) raise ManagerException(f'Unknown {_type} class: {_class}') try: return getattr(module, class_name), module_name, version except AttributeError: - self.log.exception('_get_{}_class: Unable to get class %s ' - 'from module %s', class_name, module) + self.log.exception( + '_get_{}_class: Unable to get class %s ' 'from module %s', + class_name, + module, + ) raise ManagerException(f'Unknown {_type} class: {_class}') def _build_kwargs(self, source): @@ -263,8 +303,10 @@ class Manager(object): v = environ[env_var] except KeyError: self.log.exception('Invalid provider config') - raise ManagerException('Incorrect provider config, ' - 'missing env var ' + env_var) + raise ManagerException( + 'Incorrect provider config, ' + 'missing env var ' + env_var + ) except AttributeError: pass kwargs[k] = v @@ -283,20 +325,27 @@ class Manager(object): # Move down the tree and slice name to get the remainder for the # next round of the search. where = where[parent] - name = name[:-(len(parent) + 1)] + name = name[: -(len(parent) + 1)] # `where` is now pointing at the dictionary of children for zone_name # in the tree sub_zone_names = where.keys() self.log.debug('configured_sub_zones: subs=%s', sub_zone_names) return set(sub_zone_names) - def _populate_and_plan(self, zone_name, processors, sources, targets, - desired=None, lenient=False): + def _populate_and_plan( + self, + zone_name, + processors, + sources, + targets, + desired=None, + lenient=False, + ): - self.log.debug('sync: populating, zone=%s, lenient=%s', - zone_name, lenient) - zone = Zone(zone_name, - sub_zones=self.configured_sub_zones(zone_name)) + self.log.debug( + 'sync: populating, zone=%s, lenient=%s', zone_name, lenient + ) + zone = Zone(zone_name, sub_zones=self.configured_sub_zones(zone_name)) if desired: # This is an alias zone, rather than populate it we'll copy the @@ -309,11 +358,12 @@ class Manager(object): try: source.populate(zone, lenient=lenient) except TypeError as e: - if ("unexpected keyword argument 'lenient'" - not in str(e)): + if "unexpected keyword argument 'lenient'" not in str(e): raise - self.log.warning('provider %s does not accept lenient ' - 'param', source.__class__.__name__) + self.log.warning( + 'provider %s does not accept lenient ' 'param', + source.__class__.__name__, + ) source.populate(zone) for processor in processors: @@ -324,38 +374,56 @@ class Manager(object): for target in targets: if self.include_meta: - meta = Record.new(zone, 'octodns-meta', { - 'type': 'TXT', - 'ttl': 60, - 'value': f'provider={target.id}', - }) + meta = Record.new( + zone, + 'octodns-meta', + { + 'type': 'TXT', + 'ttl': 60, + 'value': f'provider={target.id}', + }, + ) zone.add_record(meta, replace=True) try: plan = target.plan(zone, processors=processors) except TypeError as e: if "keyword argument 'processors'" not in str(e): raise - self.log.warning('provider.plan %s does not accept processors ' - 'param', target.__class__.__name__) + self.log.warning( + 'provider.plan %s does not accept processors ' 'param', + target.__class__.__name__, + ) plan = target.plan(zone) for processor in processors: - plan = processor.process_plan(plan, sources=sources, - target=target) + plan = processor.process_plan( + plan, sources=sources, target=target + ) if plan: plans.append((target, plan)) # Return the zone as it's the desired state return plans, zone - def sync(self, eligible_zones=[], eligible_sources=[], eligible_targets=[], - dry_run=True, force=False, plan_output_fh=stdout): + def sync( + self, + eligible_zones=[], + eligible_sources=[], + eligible_targets=[], + dry_run=True, + force=False, + plan_output_fh=stdout, + ): self.log.info( 'sync: eligible_zones=%s, eligible_targets=%s, dry_run=%s, ' 'force=%s, plan_output_fh=%s', - eligible_zones, eligible_targets, dry_run, force, - getattr(plan_output_fh, 'name', plan_output_fh.__class__.__name__)) + eligible_zones, + eligible_targets, + dry_run, + force, + getattr(plan_output_fh, 'name', plan_output_fh.__class__.__name__), + ) zones = self.config['zones'].items() if eligible_zones: @@ -370,19 +438,27 @@ class Manager(object): # Check that the source zone is defined. if source_zone not in self.config['zones']: - self.log.error(f'Invalid alias zone {zone_name}, ' - f'target {source_zone} does not exist') - raise ManagerException(f'Invalid alias zone {zone_name}: ' - f'source zone {source_zone} does ' - 'not exist') + self.log.error( + f'Invalid alias zone {zone_name}, ' + f'target {source_zone} does not exist' + ) + raise ManagerException( + f'Invalid alias zone {zone_name}: ' + f'source zone {source_zone} does ' + 'not exist' + ) # Check that the source zone is not an alias zone itself. if 'alias' in self.config['zones'][source_zone]: - self.log.error(f'Invalid alias zone {zone_name}, ' - f'target {source_zone} is an alias zone') - raise ManagerException(f'Invalid alias zone {zone_name}: ' - f'source zone {source_zone} is an ' - 'alias zone') + self.log.error( + f'Invalid alias zone {zone_name}, ' + f'target {source_zone} is an alias zone' + ) + raise ManagerException( + f'Invalid alias zone {zone_name}: ' + f'source zone {source_zone} is an ' + 'alias zone' + ) aliased_zones[zone_name] = source_zone continue @@ -400,8 +476,9 @@ class Manager(object): processors = config.get('processors', []) - if (eligible_sources and not - [s for s in sources if s in eligible_sources]): + if eligible_sources and not [ + s for s in sources if s in eligible_sources + ]: self.log.info('sync: no eligible sources, skipping') continue @@ -423,8 +500,9 @@ class Manager(object): collected.append(self.processors[processor]) processors = collected except KeyError: - raise ManagerException(f'Zone {zone_name}, unknown ' - f'processor: {processor}') + raise ManagerException( + f'Zone {zone_name}, unknown ' f'processor: {processor}' + ) try: # rather than using a list comprehension, we break this loop @@ -435,26 +513,35 @@ class Manager(object): collected.append(self.providers[source]) sources = collected except KeyError: - raise ManagerException(f'Zone {zone_name}, unknown ' - f'source: {source}') + raise ManagerException( + f'Zone {zone_name}, unknown ' f'source: {source}' + ) try: trgs = [] for target in targets: trg = self.providers[target] if not isinstance(trg, BaseProvider): - raise ManagerException(f'{trg} - "{target}" does not ' - 'support targeting') + raise ManagerException( + f'{trg} - "{target}" does not ' 'support targeting' + ) trgs.append(trg) targets = trgs except KeyError: - raise ManagerException(f'Zone {zone_name}, unknown ' - f'target: {target}') + raise ManagerException( + f'Zone {zone_name}, unknown ' f'target: {target}' + ) - futures.append(self._executor.submit(self._populate_and_plan, - zone_name, processors, - sources, targets, - lenient=lenient)) + futures.append( + self._executor.submit( + self._populate_and_plan, + zone_name, + processors, + sources, + targets, + lenient=lenient, + ) + ) # Wait on all results and unpack/flatten the plans and store the # desired states in case we need them below @@ -473,18 +560,22 @@ class Manager(object): try: desired_config = desired[zone_source] except KeyError: - raise ManagerException(f'Zone {zone_name} cannot be sync ' - f'without zone {zone_source} sinced ' - 'it is aliased') - futures.append(self._executor.submit( - self._populate_and_plan, - zone_name, - processors, - [], - [self.providers[t] for t in source_config['targets']], - desired=desired_config, - lenient=lenient - )) + raise ManagerException( + f'Zone {zone_name} cannot be sync ' + f'without zone {zone_source} sinced ' + 'it is aliased' + ) + futures.append( + self._executor.submit( + self._populate_and_plan, + zone_name, + processors, + [], + [self.providers[t] for t in source_config['targets']], + desired=desired_config, + lenient=lenient, + ) + ) # Wait on results and unpack/flatten the plans, ignore the desired here # as these are aliased zones @@ -514,8 +605,9 @@ class Manager(object): for target, plan in plans: zone_name = plan.existing.name if zones[zone_name].get('always-dry-run', False): - self.log.info('sync: zone=%s skipping always-dry-run', - zone_name) + self.log.info( + 'sync: zone=%s skipping always-dry-run', zone_name + ) continue total_changes += target.apply(plan) @@ -583,15 +675,19 @@ class Manager(object): if source_zone: if source_zone not in self.config['zones']: self.log.exception('Invalid alias zone') - raise ManagerException(f'Invalid alias zone {zone_name}: ' - f'source zone {source_zone} does ' - 'not exist') + raise ManagerException( + f'Invalid alias zone {zone_name}: ' + f'source zone {source_zone} does ' + 'not exist' + ) if 'alias' in self.config['zones'][source_zone]: self.log.exception('Invalid alias zone') - raise ManagerException(f'Invalid alias zone {zone_name}: ' - 'source zone {source_zone} is an ' - 'alias zone') + raise ManagerException( + f'Invalid alias zone {zone_name}: ' + 'source zone {source_zone} is an ' + 'alias zone' + ) # this is just here to satisfy coverage, see # https://github.com/nedbat/coveragepy/issues/198 @@ -613,8 +709,9 @@ class Manager(object): collected.append(self.providers[source]) sources = collected except KeyError: - raise ManagerException(f'Zone {zone_name}, unknown source: ' + - source) + raise ManagerException( + f'Zone {zone_name}, unknown source: ' + source + ) for source in sources: if isinstance(source, YamlProvider): @@ -627,13 +724,15 @@ class Manager(object): for processor in processors: collected.append(self.processors[processor]) except KeyError: - raise ManagerException(f'Zone {zone_name}, unknown ' - f'processor: {processor}') + raise ManagerException( + f'Zone {zone_name}, unknown ' f'processor: {processor}' + ) def get_zone(self, zone_name): if not zone_name[-1] == '.': - raise ManagerException(f'Invalid zone name {zone_name}, missing ' - 'ending dot') + raise ManagerException( + f'Invalid zone name {zone_name}, missing ' 'ending dot' + ) for name, config in self.config['zones'].items(): if name == zone_name: diff --git a/octodns/processor/__init__.py b/octodns/processor/__init__.py index 14ccf18..16a8eb0 100644 --- a/octodns/processor/__init__.py +++ b/octodns/processor/__init__.py @@ -2,5 +2,9 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) diff --git a/octodns/processor/acme.py b/octodns/processor/acme.py index 2d7c101..2797dba 100644 --- a/octodns/processor/acme.py +++ b/octodns/processor/acme.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger @@ -34,8 +38,9 @@ class AcmeMangingProcessor(BaseProcessor): def process_source_zone(self, desired, *args, **kwargs): for record in desired.records: - if record._type == 'TXT' and \ - record.name.startswith('_acme-challenge'): + if record._type == 'TXT' and record.name.startswith( + '_acme-challenge' + ): # We have a managed acme challenge record (owned by octoDNS) so # we should mark it as such record = record.copy() @@ -51,10 +56,12 @@ class AcmeMangingProcessor(BaseProcessor): for record in existing.records: # Uses a startswith rather than == to ignore subdomain challenges, # e.g. _acme-challenge.foo.domain.com when managing domain.com - if record._type == 'TXT' and \ - record.name.startswith('_acme-challenge') and \ - '*octoDNS*' not in record.values and \ - record not in self._owned: + if ( + record._type == 'TXT' + and record.name.startswith('_acme-challenge') + and '*octoDNS*' not in record.values + and record not in self._owned + ): self.log.info('_process: ignoring %s', record.fqdn) existing.remove_record(record) diff --git a/octodns/processor/awsacm.py b/octodns/processor/awsacm.py index f147c00..dcd53f9 100644 --- a/octodns/processor/awsacm.py +++ b/octodns/processor/awsacm.py @@ -2,21 +2,30 @@ # Ignores AWS ACM validation CNAME records. # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Route53') try: - logger.warning('octodns_route53 shimmed. Update your processor class to ' - 'octodns_route53.processor.AwsAcmMangingProcessor. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_route53 shimmed. Update your processor class to ' + 'octodns_route53.processor.AwsAcmMangingProcessor. ' + 'Shim will be removed in 1.0' + ) from octodns_route53.processor import AwsAcmMangingProcessor + AwsAcmMangingProcessor # pragma: no cover except ModuleNotFoundError: - logger.exception('AwsAcmMangingProcessor has been moved into a separate ' - 'module, octodns_route53 is now required. Processor ' - 'class should be updated to ' - 'octodns_route53.processor.AwsAcmMangingProcessor') + logger.exception( + 'AwsAcmMangingProcessor has been moved into a separate ' + 'module, octodns_route53 is now required. Processor ' + 'class should be updated to ' + 'octodns_route53.processor.AwsAcmMangingProcessor' + ) raise diff --git a/octodns/processor/base.py b/octodns/processor/base.py index 82ee66a..ac5c155 100644 --- a/octodns/processor/base.py +++ b/octodns/processor/base.py @@ -2,12 +2,15 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) class BaseProcessor(object): - def __init__(self, name): self.name = name diff --git a/octodns/processor/filter.py b/octodns/processor/filter.py index d9b8ee3..b343332 100644 --- a/octodns/processor/filter.py +++ b/octodns/processor/filter.py @@ -2,14 +2,17 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from .base import BaseProcessor class TypeAllowlistFilter(BaseProcessor): - def __init__(self, name, allowlist): super(TypeAllowlistFilter, self).__init__(name) self.allowlist = set(allowlist) @@ -26,7 +29,6 @@ class TypeAllowlistFilter(BaseProcessor): class TypeRejectlistFilter(BaseProcessor): - def __init__(self, name, rejectlist): super(TypeRejectlistFilter, self).__init__(name) self.rejectlist = set(rejectlist) diff --git a/octodns/processor/ownership.py b/octodns/processor/ownership.py index a70c8dd..68b7430 100644 --- a/octodns/processor/ownership.py +++ b/octodns/processor/ownership.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from collections import defaultdict @@ -17,7 +21,6 @@ from .base import BaseProcessor # delete. We'll take ownership of existing records that we're told to manage # and thus "own" them going forward. class OwnershipProcessor(BaseProcessor): - def __init__(self, name, txt_name='_owner', txt_value='*octodns*'): super(OwnershipProcessor, self).__init__(name) self.txt_name = txt_name @@ -32,19 +35,21 @@ class OwnershipProcessor(BaseProcessor): name = f'{self.txt_name}.{record._type}.{record_name}' else: name = f'{self.txt_name}.{record._type}' - txt = Record.new(desired, name, { - 'type': 'TXT', - 'ttl': 60, - 'value': self.txt_value, - }) + txt = Record.new( + desired, + name, + {'type': 'TXT', 'ttl': 60, 'value': self.txt_value}, + ) desired.add_record(txt) return desired def _is_ownership(self, record): - return record._type == 'TXT' and \ - record.name.startswith(self.txt_name) \ + return ( + record._type == 'TXT' + and record.name.startswith(self.txt_name) and record.values == self._txt_values + ) def process_plan(self, plan, *args, **kwargs): if not plan: @@ -80,9 +85,11 @@ class OwnershipProcessor(BaseProcessor): for change in plan.changes: record = change.record - if not self._is_ownership(record) and \ - record._type not in owned[record.name] and \ - record.name != 'octodns-meta': + if ( + not self._is_ownership(record) + and record._type not in owned[record.name] + and record.name != 'octodns-meta' + ): # It's not an ownership TXT, it's not owned, and it's not # special we're going to ignore it continue @@ -92,8 +99,13 @@ class OwnershipProcessor(BaseProcessor): filtered_changes.append(change) if plan.changes != filtered_changes: - return Plan(plan.existing, plan.desired, filtered_changes, - plan.exists, plan.update_pcent_threshold, - plan.delete_pcent_threshold) + return Plan( + plan.existing, + plan.desired, + filtered_changes, + plan.exists, + plan.update_pcent_threshold, + plan.delete_pcent_threshold, + ) return plan diff --git a/octodns/provider/__init__.py b/octodns/provider/__init__.py index 7e18783..4acfe88 100644 --- a/octodns/provider/__init__.py +++ b/octodns/provider/__init__.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) class ProviderException(Exception): diff --git a/octodns/provider/azuredns.py b/octodns/provider/azuredns.py index 3210685..269df2f 100644 --- a/octodns/provider/azuredns.py +++ b/octodns/provider/azuredns.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Azure') try: - logger.warning('octodns_azure shimmed. Update your provider class to ' - 'octodns_azure.AzureProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_azure shimmed. Update your provider class to ' + 'octodns_azure.AzureProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_azure import AzureProvider + AzureProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('AzureProvider has been moved into a separate module, ' - 'octodns_azure is now required. Provider class should ' - 'be updated to octodns_azure.AzureProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'AzureProvider has been moved into a separate module, ' + 'octodns_azure is now required. Provider class should ' + 'be updated to octodns_azure.AzureProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/base.py b/octodns/provider/base.py index b524017..78001fa 100644 --- a/octodns/provider/base.py +++ b/octodns/provider/base.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from ..source.base import BaseSource from ..zone import Zone @@ -12,19 +16,24 @@ from . import SupportsException class BaseProvider(BaseSource): - - def __init__(self, id, apply_disabled=False, - update_pcent_threshold=Plan.MAX_SAFE_UPDATE_PCENT, - delete_pcent_threshold=Plan.MAX_SAFE_DELETE_PCENT, - strict_supports=False): + def __init__( + self, + id, + apply_disabled=False, + update_pcent_threshold=Plan.MAX_SAFE_UPDATE_PCENT, + delete_pcent_threshold=Plan.MAX_SAFE_DELETE_PCENT, + strict_supports=False, + ): super(BaseProvider, self).__init__(id) - self.log.debug('__init__: id=%s, apply_disabled=%s, ' - 'update_pcent_threshold=%.2f, ' - 'delete_pcent_threshold=%.2f', - id, - apply_disabled, - update_pcent_threshold, - delete_pcent_threshold) + self.log.debug( + '__init__: id=%s, apply_disabled=%s, ' + 'update_pcent_threshold=%.2f, ' + 'delete_pcent_threshold=%.2f', + id, + apply_disabled, + update_pcent_threshold, + delete_pcent_threshold, + ) self.apply_disabled = apply_disabled self.update_pcent_threshold = update_pcent_threshold self.delete_pcent_threshold = delete_pcent_threshold @@ -68,8 +77,10 @@ class BaseProvider(BaseSource): if not unsupported_pools: continue unsupported_pools = ','.join(unsupported_pools) - msg = f'"status" flag used in pools {unsupported_pools}' \ + msg = ( + f'"status" flag used in pools {unsupported_pools}' f' in {record.fqdn} is not supported' + ) fallback = 'will ignore it and respect the healthcheck' self.supports_warn_or_except(msg, fallback) record = record.copy() @@ -84,11 +95,13 @@ class BaseProvider(BaseSource): record = record.copy() record.dynamic = None desired.add_record(record, replace=True) - elif record._type == 'PTR' and len(record.values) > 1 and \ - not self.SUPPORTS_MULTIVALUE_PTR: + elif ( + record._type == 'PTR' + and len(record.values) > 1 + and not self.SUPPORTS_MULTIVALUE_PTR + ): # replace with a single-value copy - msg = \ - f'multi-value PTR records not supported for {record.fqdn}' + msg = f'multi-value PTR records not supported for {record.fqdn}' fallback = f'falling back to single value, {record.value}' self.supports_warn_or_except(msg, fallback) record = record.copy() @@ -98,13 +111,15 @@ class BaseProvider(BaseSource): record = desired.root_ns if self.SUPPORTS_ROOT_NS: if not record: - self.log.warning('root NS record supported, but no record ' - 'is configured for %s', desired.name) + self.log.warning( + 'root NS record supported, but no record ' + 'is configured for %s', + desired.name, + ) else: if record: # we can't manage root NS records, get rid of it - msg = \ - f'root NS record not supported for {record.fqdn}' + msg = f'root NS record not supported for {record.fqdn}' fallback = 'ignoring it' self.supports_warn_or_except(msg, fallback) desired.remove_record(record) @@ -132,10 +147,13 @@ class BaseProvider(BaseSource): ''' existing_root_ns = existing.root_ns - if existing_root_ns and (not self.SUPPORTS_ROOT_NS or - not desired.root_ns): - self.log.info('root NS record in existing, but not supported or ' - 'not configured; ignoring it') + if existing_root_ns and ( + not self.SUPPORTS_ROOT_NS or not desired.root_ns + ): + self.log.info( + 'root NS record in existing, but not supported or ' + 'not configured; ignoring it' + ) existing.remove_record(existing_root_ns) return existing @@ -168,8 +186,10 @@ class BaseProvider(BaseSource): if exists is None: # 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) + self.log.warning( + 'Provider %s used in target mode did not return ' 'exists', + self.id, + ) # Make a (shallow) copy of the desired state so that everything from # now on (in this target) can modify it as they see fit without @@ -194,17 +214,25 @@ class BaseProvider(BaseSource): self.log.info('plan: filtered out %s changes', before - after) # allow the provider to add extra changes it needs - extra = self._extra_changes(existing=existing, desired=desired, - changes=changes) + extra = self._extra_changes( + existing=existing, desired=desired, changes=changes + ) if extra: - self.log.info('plan: extra changes\n %s', '\n ' - .join([str(c) for c in extra])) + self.log.info( + 'plan: extra changes\n %s', + '\n '.join([str(c) for c in extra]), + ) changes += extra if changes: - plan = Plan(existing, desired, changes, exists, - self.update_pcent_threshold, - self.delete_pcent_threshold) + plan = Plan( + existing, + desired, + changes, + exists, + self.update_pcent_threshold, + self.delete_pcent_threshold, + ) self.log.info('plan: %s', plan) return plan self.log.info('plan: No changes') @@ -221,11 +249,11 @@ class BaseProvider(BaseSource): zone_name = plan.desired.name num_changes = len(plan.changes) - self.log.info('apply: making %d changes to %s', num_changes, - zone_name) + self.log.info('apply: making %d changes to %s', num_changes, zone_name) self._apply(plan) 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/cloudflare.py b/octodns/provider/cloudflare.py index c1a8f56..0014620 100644 --- a/octodns/provider/cloudflare.py +++ b/octodns/provider/cloudflare.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Cloudflare') try: - logger.warning('octodns_cloudflare shimmed. Update your provider class to ' - 'octodns_cloudflare.CloudflareProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_cloudflare shimmed. Update your provider class to ' + 'octodns_cloudflare.CloudflareProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_cloudflare import CloudflareProvider + CloudflareProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('CloudflareProvider has been moved into a separate ' - 'module, octodns_cloudflare is now required. Provider ' - 'class should be updated to ' - 'octodns_cloudflare.CloudflareProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'CloudflareProvider has been moved into a separate ' + 'module, octodns_cloudflare is now required. Provider ' + 'class should be updated to ' + 'octodns_cloudflare.CloudflareProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/constellix.py b/octodns/provider/constellix.py index 5159951..5747e9a 100644 --- a/octodns/provider/constellix.py +++ b/octodns/provider/constellix.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Constellix') try: - logger.warning('octodns_constellix shimmed. Update your provider class to ' - 'octodns_constellix.ConstellixProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_constellix shimmed. Update your provider class to ' + 'octodns_constellix.ConstellixProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_constellix import ConstellixProvider + ConstellixProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('ConstellixProvider has been moved into a separate ' - 'module, octodns_constellix is now required. Provider ' - 'class should be updated to ' - 'octodns_constellix.ConstellixProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'ConstellixProvider has been moved into a separate ' + 'module, octodns_constellix is now required. Provider ' + 'class should be updated to ' + 'octodns_constellix.ConstellixProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/digitalocean.py b/octodns/provider/digitalocean.py index 9ea59b1..3e1f75e 100644 --- a/octodns/provider/digitalocean.py +++ b/octodns/provider/digitalocean.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('DigitalOcean') try: - logger.warning('octodns_digitalocean shimmed. Update your provider class ' - 'to octodns_digitalocean.DigitalOceanProvider. Shim will ' - 'be removed in 1.0') + logger.warning( + 'octodns_digitalocean shimmed. Update your provider class ' + 'to octodns_digitalocean.DigitalOceanProvider. Shim will ' + 'be removed in 1.0' + ) from octodns_digitalocean import DigitalOceanProvider + DigitalOceanProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('DigitalOceanProvider has been moved into a separate ' - 'module, octodns_digitalocean is now required. Provider ' - 'class should be updated to ' - 'octodns_digitalocean.DigitalOceanProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'DigitalOceanProvider has been moved into a separate ' + 'module, octodns_digitalocean is now required. Provider ' + 'class should be updated to ' + 'octodns_digitalocean.DigitalOceanProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/dnsimple.py b/octodns/provider/dnsimple.py index cd379a1..e34a87f 100644 --- a/octodns/provider/dnsimple.py +++ b/octodns/provider/dnsimple.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Dnsimple') try: - logger.warning('octodns_dnsimple shimmed. Update your provider class to ' - 'octodns_dnsimple.DnsimpleProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_dnsimple shimmed. Update your provider class to ' + 'octodns_dnsimple.DnsimpleProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_dnsimple import DnsimpleProvider + DnsimpleProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('DnsimpleProvider has been moved into a separate module, ' - 'octodns_dnsimple is now required. Provider class should ' - 'be updated to octodns_dnsimple.DnsimpleProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'DnsimpleProvider has been moved into a separate module, ' + 'octodns_dnsimple is now required. Provider class should ' + 'be updated to octodns_dnsimple.DnsimpleProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/dnsmadeeasy.py b/octodns/provider/dnsmadeeasy.py index fc54c38..cbb28f0 100644 --- a/octodns/provider/dnsmadeeasy.py +++ b/octodns/provider/dnsmadeeasy.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('DnsMadeEasy') try: - logger.warning('octodns_dnsmadeeasy shimmed. Update your provider class ' - 'to octodns_dnsmadeeasy.DnsMadeEasyProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_dnsmadeeasy shimmed. Update your provider class ' + 'to octodns_dnsmadeeasy.DnsMadeEasyProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_dnsmadeeasy import DnsMadeEasyProvider + DnsMadeEasyProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('DnsMadeEasyProvider has been moved into a separate ' - 'module, octodns_dnsmadeeasy is now required. Provider ' - 'class should be updated to ' - 'octodns_dnsmadeeasy.DnsMadeEasyProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'DnsMadeEasyProvider has been moved into a separate ' + 'module, octodns_dnsmadeeasy is now required. Provider ' + 'class should be updated to ' + 'octodns_dnsmadeeasy.DnsMadeEasyProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/dyn.py b/octodns/provider/dyn.py index eef3a7a..e7c1120 100644 --- a/octodns/provider/dyn.py +++ b/octodns/provider/dyn.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Dyn') try: - logger.warning('octodns_dyn shimmed. Update your provider class to ' - 'octodns_dyn.DynProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_dyn shimmed. Update your provider class to ' + 'octodns_dyn.DynProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_dyn import DynProvider + DynProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('DynProvider has been moved into a separate module, ' - 'octodns_dyn is now required. Provider class should ' - 'be updated to octodns_dyn.DynProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'DynProvider has been moved into a separate module, ' + 'octodns_dyn is now required. Provider class should ' + 'be updated to octodns_dyn.DynProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/easydns.py b/octodns/provider/easydns.py index 7be551b..6dc5c8b 100644 --- a/octodns/provider/easydns.py +++ b/octodns/provider/easydns.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('EasyDns') try: - logger.warning('octodns_easydns shimmed. Update your provider class to ' - 'octodns_easydns.EasyDnsProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_easydns shimmed. Update your provider class to ' + 'octodns_easydns.EasyDnsProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_easydns import EasyDnsProvider, EasyDNSProvider + EasyDnsProvider # pragma: no cover EasyDNSProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('EasyDNSProvider has been moved into a separate module, ' - 'octodns_easydns is now required. Provider class should ' - 'be updated to octodns_easydns.EasyDnsProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'EasyDNSProvider has been moved into a separate module, ' + 'octodns_easydns is now required. Provider class should ' + 'be updated to octodns_easydns.EasyDnsProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/edgedns.py b/octodns/provider/edgedns.py index 8e1e58c..ca439ee 100644 --- a/octodns/provider/edgedns.py +++ b/octodns/provider/edgedns.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Akamai') try: - logger.warning('octodns_edgedns shimmed. Update your provider class to ' - 'octodns_edgedns.AkamaiProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_edgedns shimmed. Update your provider class to ' + 'octodns_edgedns.AkamaiProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_edgedns import AkamaiProvider + AkamaiProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('AkamaiProvider has been moved into a separate module, ' - 'octodns_edgedns is now required. Provider class should ' - 'be updated to octodns_edgedns.AkamaiProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'AkamaiProvider has been moved into a separate module, ' + 'octodns_edgedns is now required. Provider class should ' + 'be updated to octodns_edgedns.AkamaiProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/etc_hosts.py b/octodns/provider/etc_hosts.py index 4ae9ee9..e903dd2 100644 --- a/octodns/provider/etc_hosts.py +++ b/octodns/provider/etc_hosts.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('EtcHosts') try: - logger.warning('octodns_etchosts shimmed. Update your provider class to ' - 'octodns_etchosts.EtcHostsProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_etchosts shimmed. Update your provider class to ' + 'octodns_etchosts.EtcHostsProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_etchosts import EtcHostsProvider + EtcHostsProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('EtcHostsProvider has been moved into a separate module, ' - 'octodns_etchosts is now required. Provider class should ' - 'be updated to octodns_etchosts.EtcHostsProvider. See ' - 'See https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'EtcHostsProvider has been moved into a separate module, ' + 'octodns_etchosts is now required. Provider class should ' + 'be updated to octodns_etchosts.EtcHostsProvider. See ' + 'See https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/fastdns.py b/octodns/provider/fastdns.py index 9dd1490..0164be5 100644 --- a/octodns/provider/fastdns.py +++ b/octodns/provider/fastdns.py @@ -2,14 +2,20 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Akamai') -logger.warning('AkamaiProvider has been moved into a separate module, ' - 'octodns_edgedns is now required. Provider class should ' - 'be updated to octodns_edgedns.AkamaiProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') +logger.warning( + 'AkamaiProvider has been moved into a separate module, ' + 'octodns_edgedns is now required. Provider class should ' + 'be updated to octodns_edgedns.AkamaiProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' +) diff --git a/octodns/provider/gandi.py b/octodns/provider/gandi.py index 2429bfa..4e08be0 100644 --- a/octodns/provider/gandi.py +++ b/octodns/provider/gandi.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Gandi') try: - logger.warning('octodns_gandi shimmed. Update your provider class to ' - 'octodns_gandi.GandiProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_gandi shimmed. Update your provider class to ' + 'octodns_gandi.GandiProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_gandi import GandiProvider + GandiProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('GandiProvider has been moved into a separate module, ' - 'octodns_gandi is now required. Provider class should ' - 'be updated to octodns_gandi.GandiProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'GandiProvider has been moved into a separate module, ' + 'octodns_gandi is now required. Provider class should ' + 'be updated to octodns_gandi.GandiProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/gcore.py b/octodns/provider/gcore.py index 141ed10..983ff1d 100644 --- a/octodns/provider/gcore.py +++ b/octodns/provider/gcore.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('GCore') try: - logger.warning('octodns_gcore shimmed. Update your provider class to ' - 'octodns_gcore.GCoreProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_gcore shimmed. Update your provider class to ' + 'octodns_gcore.GCoreProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_gcore import GCoreProvider + GCoreProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('GCoreProvider has been moved into a separate module, ' - 'octodns_gcore is now required. Provider class should ' - 'be updated to octodns_gcore.GCoreProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'GCoreProvider has been moved into a separate module, ' + 'octodns_gcore is now required. Provider class should ' + 'be updated to octodns_gcore.GCoreProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/googlecloud.py b/octodns/provider/googlecloud.py index 22cb07a..b772f36 100644 --- a/octodns/provider/googlecloud.py +++ b/octodns/provider/googlecloud.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('GoogleCloud') try: - logger.warning('octodns_googlecloud shimmed. Update your provider class ' - 'to octodns_googlecloud.GoogleCloudProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_googlecloud shimmed. Update your provider class ' + 'to octodns_googlecloud.GoogleCloudProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_googlecloud import GoogleCloudProvider + GoogleCloudProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('GoogleCloudProvider has been moved into a separate ' - 'module, octodns_googlecloud is now required. Provider ' - 'class should be updated to ' - 'octodns_googlecloud.GoogleCloudProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'GoogleCloudProvider has been moved into a separate ' + 'module, octodns_googlecloud is now required. Provider ' + 'class should be updated to ' + 'octodns_googlecloud.GoogleCloudProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/hetzner.py b/octodns/provider/hetzner.py index 38577e4..b0f362e 100644 --- a/octodns/provider/hetzner.py +++ b/octodns/provider/hetzner.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Hetzner') try: - logger.warning('octodns_hetzner shimmed. Update your provider class to ' - 'octodns_hetzner.HetznerProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_hetzner shimmed. Update your provider class to ' + 'octodns_hetzner.HetznerProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_hetzner import HetznerProvider + HetznerProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('HetznerProvider has been moved into a separate module, ' - 'octodns_hetzner is now required. Provider class should ' - 'be updated to octodns_hetzner.HetznerProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'HetznerProvider has been moved into a separate module, ' + 'octodns_hetzner is now required. Provider class should ' + 'be updated to octodns_hetzner.HetznerProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/mythicbeasts.py b/octodns/provider/mythicbeasts.py index 0d1378e..708124a 100644 --- a/octodns/provider/mythicbeasts.py +++ b/octodns/provider/mythicbeasts.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('MythicBeasts') try: - logger.warning('octodns_mythicbeasts shimmed. Update your provider class ' - 'to octodns_mythicbeasts.MythicBeastsProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_mythicbeasts shimmed. Update your provider class ' + 'to octodns_mythicbeasts.MythicBeastsProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_mythicbeasts import MythicBeastsProvider + MythicBeastsProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('MythicBeastsProvider has been moved into a separate ' - 'module, octodns_mythicbeasts is now required. Provider ' - 'class should be updated to ' - 'octodns_mythicbeasts.MythicBeastsProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'MythicBeastsProvider has been moved into a separate ' + 'module, octodns_mythicbeasts is now required. Provider ' + 'class should be updated to ' + 'octodns_mythicbeasts.MythicBeastsProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/ns1.py b/octodns/provider/ns1.py index 76398a8..9d60570 100644 --- a/octodns/provider/ns1.py +++ b/octodns/provider/ns1.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Ns1') try: - logger.warning('octodns_ns1 shimmed. Update your provider class to ' - 'octodns_ns1.Ns1Provider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_ns1 shimmed. Update your provider class to ' + 'octodns_ns1.Ns1Provider. ' + 'Shim will be removed in 1.0' + ) from octodns_ns1 import Ns1Provider + Ns1Provider # pragma: no cover except ModuleNotFoundError: - logger.exception('Ns1Provider has been moved into a separate module, ' - 'octodns_ns1 is now required. Provider class should ' - 'be updated to octodns_ns1.Ns1Provider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'Ns1Provider has been moved into a separate module, ' + 'octodns_ns1 is now required. Provider class should ' + 'be updated to octodns_ns1.Ns1Provider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/ovh.py b/octodns/provider/ovh.py index 5f83560..de6f045 100644 --- a/octodns/provider/ovh.py +++ b/octodns/provider/ovh.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Ovh') try: - logger.warning('octodns_ovh shimmed. Update your provider class to ' - 'octodns_ovh.OvhProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_ovh shimmed. Update your provider class to ' + 'octodns_ovh.OvhProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_ovh import OvhProvider + OvhProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('OvhProvider has been moved into a separate module, ' - 'octodns_ovh is now required. Provider class should ' - 'be updated to octodns_ovh.OvhProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'OvhProvider has been moved into a separate module, ' + 'octodns_ovh is now required. Provider class should ' + 'be updated to octodns_ovh.OvhProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/plan.py b/octodns/provider/plan.py index 2d39c80..a436483 100644 --- a/octodns/provider/plan.py +++ b/octodns/provider/plan.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import DEBUG, ERROR, INFO, WARN, getLogger from sys import stdout @@ -16,30 +20,37 @@ class UnsafePlan(Exception): class RootNsChange(UnsafePlan): - def __init__(self): super().__init__('Root NS record change, force required') class TooMuchChange(UnsafePlan): - - def __init__(self, why, update_pcent, update_threshold, change_count, - existing_count): - msg = f'{why}, {update_pcent:.2f}% is over {update_threshold:.2f}% ' \ + def __init__( + self, why, update_pcent, update_threshold, change_count, existing_count + ): + msg = ( + f'{why}, {update_pcent:.2f}% is over {update_threshold:.2f}% ' f'({change_count}/{existing_count}), force required' + ) super().__init__(msg) class Plan(object): log = getLogger('Plan') - MAX_SAFE_UPDATE_PCENT = .3 - MAX_SAFE_DELETE_PCENT = .3 + MAX_SAFE_UPDATE_PCENT = 0.3 + MAX_SAFE_DELETE_PCENT = 0.3 MIN_EXISTING_RECORDS = 10 - def __init__(self, existing, desired, changes, exists, - update_pcent_threshold=MAX_SAFE_UPDATE_PCENT, - delete_pcent_threshold=MAX_SAFE_DELETE_PCENT): + def __init__( + self, + existing, + desired, + changes, + exists, + update_pcent_threshold=MAX_SAFE_UPDATE_PCENT, + delete_pcent_threshold=MAX_SAFE_DELETE_PCENT, + ): self.existing = existing self.desired = desired # Sort changes to ensure we always have a consistent ordering for @@ -51,11 +62,7 @@ class Plan(object): self.update_pcent_threshold = update_pcent_threshold self.delete_pcent_threshold = delete_pcent_threshold - change_counts = { - 'Create': 0, - 'Delete': 0, - 'Update': 0 - } + change_counts = {'Create': 0, 'Delete': 0, 'Update': 0} for change in changes: change_counts[change.__class__.__name__] += 1 self.change_counts = change_counts @@ -65,43 +72,57 @@ class Plan(object): except AttributeError: existing_n = 0 - self.log.debug('__init__: Creates=%d, Updates=%d, Deletes=%d ' - 'Existing=%d', - self.change_counts['Create'], - self.change_counts['Update'], - self.change_counts['Delete'], existing_n) + self.log.debug( + '__init__: Creates=%d, Updates=%d, Deletes=%d ' 'Existing=%d', + self.change_counts['Create'], + self.change_counts['Update'], + self.change_counts['Delete'], + existing_n, + ) def raise_if_unsafe(self): # TODO: what is safe really? - if self.existing and \ - len(self.existing.records) >= self.MIN_EXISTING_RECORDS: + if ( + self.existing + and len(self.existing.records) >= self.MIN_EXISTING_RECORDS + ): existing_record_count = len(self.existing.records) if existing_record_count > 0: - update_pcent = (self.change_counts['Update'] / - existing_record_count) - delete_pcent = (self.change_counts['Delete'] / - existing_record_count) + update_pcent = ( + self.change_counts['Update'] / existing_record_count + ) + delete_pcent = ( + self.change_counts['Delete'] / existing_record_count + ) else: update_pcent = 0 delete_pcent = 0 if update_pcent > self.update_pcent_threshold: - raise TooMuchChange('Too many updates', update_pcent * 100, - self.update_pcent_threshold * 100, - self.change_counts['Update'], - existing_record_count) + raise TooMuchChange( + 'Too many updates', + update_pcent * 100, + self.update_pcent_threshold * 100, + self.change_counts['Update'], + existing_record_count, + ) if delete_pcent > self.delete_pcent_threshold: - raise TooMuchChange('Too many deletes', delete_pcent * 100, - self.delete_pcent_threshold * 100, - self.change_counts['Delete'], - existing_record_count) + raise TooMuchChange( + 'Too many deletes', + delete_pcent * 100, + self.delete_pcent_threshold * 100, + self.change_counts['Delete'], + existing_record_count, + ) # If we have any changes of the root NS record for the zone it's a huge # deal and force should always be required for extra care - if self.exists and any(c for c in self.changes - if c.record and c.record._type == 'NS' and - c.record.name == ''): + if self.exists and any( + c + for c in self.changes + if c.record and c.record._type == 'NS' and c.record.name == '' + ): raise RootNsChange() def __repr__(self): @@ -109,18 +130,18 @@ class Plan(object): updates = self.change_counts['Update'] deletes = self.change_counts['Delete'] existing = len(self.existing.records) - return f'Creates={creates}, Updates={updates}, Deletes={deletes}, ' \ + return ( + f'Creates={creates}, Updates={updates}, Deletes={deletes}, ' f'Existing Records={existing}' + ) class _PlanOutput(object): - def __init__(self, name): self.name = name class PlanLogger(_PlanOutput): - def __init__(self, name, level='info'): super(PlanLogger, self).__init__(name) try: @@ -129,14 +150,16 @@ class PlanLogger(_PlanOutput): 'info': INFO, 'warn': WARN, 'warning': WARN, - 'error': ERROR + 'error': ERROR, }[level.lower()] except (AttributeError, KeyError): raise Exception(f'Unsupported level: {level}') def run(self, log, plans, *args, **kwargs): - hr = '*************************************************************' \ + hr = ( + '*************************************************************' '*******************\n' + ) buf = StringIO() buf.write('\n') if plans: @@ -188,7 +211,6 @@ def _value_stringifier(record, sep): class PlanMarkdown(_PlanOutput): - def run(self, plans, fh=stdout, *args, **kwargs): if plans: current_zone = None @@ -203,8 +225,10 @@ class PlanMarkdown(_PlanOutput): fh.write(target.id) fh.write('\n\n') - fh.write('| Operation | Name | Type | TTL | Value | Source |\n' - '|--|--|--|--|--|--|\n') + fh.write( + '| Operation | Name | Type | TTL | Value | Source |\n' + '|--|--|--|--|--|--|\n' + ) if plan.exists is False: fh.write('| Create | ') @@ -248,7 +272,6 @@ class PlanMarkdown(_PlanOutput): class PlanHtml(_PlanOutput): - def run(self, plans, fh=stdout, *args, **kwargs): if plans: current_zone = None @@ -261,7 +284,8 @@ class PlanHtml(_PlanOutput): fh.write('

') fh.write(target.id) - fh.write('''

+ fh.write( + ''' @@ -271,7 +295,8 @@ class PlanHtml(_PlanOutput): -''') +''' + ) if plan.exists is False: fh.write(' \n \n ' in out) + self.assertTrue( + ' ' in out + ) class TestPlanMarkdown(TestCase): @@ -139,7 +154,6 @@ class TestPlanMarkdown(TestCase): class HelperPlan(Plan): - def __init__(self, *args, min_existing=0, **kwargs): super().__init__(*args, **kwargs) self.MIN_EXISTING_RECORDS = min_existing @@ -147,26 +161,18 @@ class HelperPlan(Plan): class TestPlanSafety(TestCase): existing = Zone('unit.tests.', []) - record_1 = Record.new(existing, '1', data={ - 'type': 'A', - 'ttl': 42, - 'value': '1.2.3.4', - }) - record_2 = Record.new(existing, '2', data={ - 'type': 'A', - 'ttl': 42, - 'value': '1.2.3.4', - }) - record_3 = Record.new(existing, '3', data={ - 'type': 'A', - 'ttl': 42, - 'value': '1.2.3.4', - }) - record_4 = Record.new(existing, '4', data={ - 'type': 'A', - 'ttl': 42, - 'value': '1.2.3.4', - }) + record_1 = Record.new( + existing, '1', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'} + ) + record_2 = Record.new( + existing, '2', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'} + ) + record_3 = Record.new( + existing, '3', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'} + ) + record_4 = Record.new( + existing, '4', data={'type': 'A', 'ttl': 42, 'value': '1.2.3.4'} + ) def test_too_many_updates(self): existing = self.existing.copy() @@ -267,11 +273,15 @@ class TestPlanSafety(TestCase): plan.raise_if_unsafe() # Add a change to a non-root NS record, we're OK - ns_record = Record.new(existing, 'sub', data={ - 'type': 'NS', - 'ttl': 43, - 'values': ('ns1.unit.tests.', 'ns1.unit.tests.'), - }) + ns_record = Record.new( + existing, + 'sub', + data={ + 'type': 'NS', + 'ttl': 43, + 'values': ('ns1.unit.tests.', 'ns1.unit.tests.'), + }, + ) changes.append(Delete(ns_record)) plan = HelperPlan(existing, None, changes, True) plan.raise_if_unsafe() @@ -279,11 +289,15 @@ class TestPlanSafety(TestCase): changes.pop(-1) # Delete the root NS record and we get an unsafe - root_ns_record = Record.new(existing, '', data={ - 'type': 'NS', - 'ttl': 43, - 'values': ('ns3.unit.tests.', 'ns4.unit.tests.'), - }) + root_ns_record = Record.new( + existing, + '', + data={ + 'type': 'NS', + 'ttl': 43, + 'values': ('ns3.unit.tests.', 'ns4.unit.tests.'), + }, + ) changes.append(Delete(root_ns_record)) plan = HelperPlan(existing, None, changes, True) with self.assertRaises(RootNsChange) as ctx: diff --git a/tests/test_octodns_processor_acme.py b/tests/test_octodns_processor_acme.py index 505d15c..78a4f5d 100644 --- a/tests/test_octodns_processor_acme.py +++ b/tests/test_octodns_processor_acme.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase @@ -13,46 +17,43 @@ from octodns.zone import Zone zone = Zone('unit.tests.', []) records = { - 'root-unowned': Record.new(zone, '_acme-challenge', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'magic bit', - }), - 'sub-unowned': Record.new(zone, '_acme-challenge.sub-unowned', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'magic bit', - }), - 'not-txt': Record.new(zone, '_acme-challenge.not-txt', { - 'ttl': 30, - 'type': 'AAAA', - 'value': '::1', - }), - 'not-acme': Record.new(zone, 'not-acme', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'Hello World!', - }), - 'managed': Record.new(zone, '_acme-challenge.managed', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'magic bit', - }), - 'owned': Record.new(zone, '_acme-challenge.owned', { - 'ttl': 30, - 'type': 'TXT', - 'values': ['*octoDNS*', 'magic bit'], - }), - 'going-away': Record.new(zone, '_acme-challenge.going-away', { - 'ttl': 30, - 'type': 'TXT', - 'values': ['*octoDNS*', 'magic bit'], - }), + 'root-unowned': Record.new( + zone, + '_acme-challenge', + {'ttl': 30, 'type': 'TXT', 'value': 'magic bit'}, + ), + 'sub-unowned': Record.new( + zone, + '_acme-challenge.sub-unowned', + {'ttl': 30, 'type': 'TXT', 'value': 'magic bit'}, + ), + 'not-txt': Record.new( + zone, + '_acme-challenge.not-txt', + {'ttl': 30, 'type': 'AAAA', 'value': '::1'}, + ), + 'not-acme': Record.new( + zone, 'not-acme', {'ttl': 30, 'type': 'TXT', 'value': 'Hello World!'} + ), + 'managed': Record.new( + zone, + '_acme-challenge.managed', + {'ttl': 30, 'type': 'TXT', 'value': 'magic bit'}, + ), + 'owned': Record.new( + zone, + '_acme-challenge.owned', + {'ttl': 30, 'type': 'TXT', 'values': ['*octoDNS*', 'magic bit']}, + ), + 'going-away': Record.new( + zone, + '_acme-challenge.going-away', + {'ttl': 30, 'type': 'TXT', 'values': ['*octoDNS*', 'magic bit']}, + ), } class TestAcmeMangingProcessor(TestCase): - def test_process_zones(self): acme = AcmeMangingProcessor('acme') @@ -64,11 +65,10 @@ class TestAcmeMangingProcessor(TestCase): source.add_record(records['managed']) got = acme.process_source_zone(source) - self.assertEqual([ - '_acme-challenge.managed', - '_acme-challenge.not-txt', - 'not-acme', - ], sorted([r.name for r in got.records])) + self.assertEqual( + ['_acme-challenge.managed', '_acme-challenge.not-txt', 'not-acme'], + sorted([r.name for r in got.records]), + ) managed = None for record in got.records: if record.name.endswith('managed'): @@ -93,10 +93,13 @@ class TestAcmeMangingProcessor(TestCase): existing.add_record(records['going-away']) got = acme.process_target_zone(existing) - self.assertEqual([ - '_acme-challenge.going-away', - '_acme-challenge.managed', - '_acme-challenge.not-txt', - '_acme-challenge.owned', - 'not-acme' - ], sorted([r.name for r in got.records])) + self.assertEqual( + [ + '_acme-challenge.going-away', + '_acme-challenge.managed', + '_acme-challenge.not-txt', + '_acme-challenge.owned', + 'not-acme', + ], + sorted([r.name for r in got.records]), + ) diff --git a/tests/test_octodns_processor_awsacm.py b/tests/test_octodns_processor_awsacm.py index 3a99447..8d9e071 100644 --- a/tests/test_octodns_processor_awsacm.py +++ b/tests/test_octodns_processor_awsacm.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestAwsAcmMangingProcessor(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.processor.awsacm import AwsAcmMangingProcessor + AwsAcmMangingProcessor diff --git a/tests/test_octodns_processor_filter.py b/tests/test_octodns_processor_filter.py index d4a38ec..859677d 100644 --- a/tests/test_octodns_processor_filter.py +++ b/tests/test_octodns_processor_filter.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase @@ -13,37 +17,20 @@ from octodns.zone import Zone zone = Zone('unit.tests.', []) for record in [ - Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }), - Record.new(zone, 'aaaa', { - 'ttl': 30, - 'type': 'AAAA', - 'value': '::1', - }), - Record.new(zone, 'txt', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'Hello World!', - }), - Record.new(zone, 'a2', { - 'ttl': 30, - 'type': 'A', - 'value': '2.3.4.5', - }), - Record.new(zone, 'txt2', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'That will do', - }), + Record.new(zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}), + Record.new(zone, 'aaaa', {'ttl': 30, 'type': 'AAAA', 'value': '::1'}), + Record.new( + zone, 'txt', {'ttl': 30, 'type': 'TXT', 'value': 'Hello World!'} + ), + Record.new(zone, 'a2', {'ttl': 30, 'type': 'A', 'value': '2.3.4.5'}), + Record.new( + zone, 'txt2', {'ttl': 30, 'type': 'TXT', 'value': 'That will do'} + ), ]: zone.add_record(record) class TestTypeAllowListFilter(TestCase): - def test_basics(self): filter_a = TypeAllowlistFilter('only-a', set(('A'))) @@ -56,35 +43,36 @@ class TestTypeAllowListFilter(TestCase): filter_txt = TypeAllowlistFilter('only-txt', ['TXT']) got = filter_txt.process_target_zone(zone.copy()) - self.assertEqual(['txt', 'txt2'], - sorted([r.name for r in got.records])) + self.assertEqual(['txt', 'txt2'], sorted([r.name for r in got.records])) filter_a_aaaa = TypeAllowlistFilter('only-aaaa', set(('A', 'AAAA'))) got = filter_a_aaaa.process_target_zone(zone.copy()) - self.assertEqual(['a', 'a2', 'aaaa'], - sorted([r.name for r in got.records])) + self.assertEqual( + ['a', 'a2', 'aaaa'], sorted([r.name for r in got.records]) + ) class TestTypeRejectListFilter(TestCase): - def test_basics(self): filter_a = TypeRejectlistFilter('not-a', set(('A'))) got = filter_a.process_source_zone(zone.copy()) - self.assertEqual(['aaaa', 'txt', 'txt2'], - sorted([r.name for r in got.records])) + self.assertEqual( + ['aaaa', 'txt', 'txt2'], sorted([r.name for r in got.records]) + ) filter_aaaa = TypeRejectlistFilter('not-aaaa', ('AAAA',)) got = filter_aaaa.process_source_zone(zone.copy()) - self.assertEqual(['a', 'a2', 'txt', 'txt2'], - sorted([r.name for r in got.records])) + self.assertEqual( + ['a', 'a2', 'txt', 'txt2'], sorted([r.name for r in got.records]) + ) filter_txt = TypeRejectlistFilter('not-txt', ['TXT']) got = filter_txt.process_target_zone(zone.copy()) - self.assertEqual(['a', 'a2', 'aaaa'], - sorted([r.name for r in got.records])) + self.assertEqual( + ['a', 'a2', 'aaaa'], sorted([r.name for r in got.records]) + ) filter_a_aaaa = TypeRejectlistFilter('not-a-aaaa', set(('A', 'AAAA'))) got = filter_a_aaaa.process_target_zone(zone.copy()) - self.assertEqual(['txt', 'txt2'], - sorted([r.name for r in got.records])) + self.assertEqual(['txt', 'txt2'], sorted([r.name for r in got.records])) diff --git a/tests/test_octodns_processor_ownership.py b/tests/test_octodns_processor_ownership.py index a989926..debfacc 100644 --- a/tests/test_octodns_processor_ownership.py +++ b/tests/test_octodns_processor_ownership.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase @@ -17,57 +21,40 @@ from helpers import PlannableProvider zone = Zone('unit.tests.', []) records = {} for record in [ - Record.new(zone, '', { - 'ttl': 30, - 'type': 'A', - 'values': [ - '1.2.3.4', - '5.6.7.8', - ], - }), - Record.new(zone, 'the-a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }), - Record.new(zone, 'the-aaaa', { - 'ttl': 30, - 'type': 'AAAA', - 'value': '::1', - }), - Record.new(zone, 'the-txt', { - 'ttl': 30, - 'type': 'TXT', - 'value': 'Hello World!', - }), - Record.new(zone, '*', { - 'ttl': 30, - 'type': 'A', - 'value': '4.3.2.1', - }), + Record.new( + zone, '', {'ttl': 30, 'type': 'A', 'values': ['1.2.3.4', '5.6.7.8']} + ), + Record.new(zone, 'the-a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'}), + Record.new(zone, 'the-aaaa', {'ttl': 30, 'type': 'AAAA', 'value': '::1'}), + Record.new( + zone, 'the-txt', {'ttl': 30, 'type': 'TXT', 'value': 'Hello World!'} + ), + Record.new(zone, '*', {'ttl': 30, 'type': 'A', 'value': '4.3.2.1'}), ]: records[record.name] = record zone.add_record(record) class TestOwnershipProcessor(TestCase): - def test_process_source_zone(self): ownership = OwnershipProcessor('ownership') got = ownership.process_source_zone(zone.copy()) - self.assertEqual([ - '', - '*', - '_owner.a', - '_owner.a._wildcard', - '_owner.a.the-a', - '_owner.aaaa.the-aaaa', - '_owner.txt.the-txt', - 'the-a', - 'the-aaaa', - 'the-txt', - ], sorted([r.name for r in got.records])) + self.assertEqual( + [ + '', + '*', + '_owner.a', + '_owner.a._wildcard', + '_owner.a.the-a', + '_owner.aaaa.the-aaaa', + '_owner.txt.the-txt', + 'the-a', + 'the-aaaa', + 'the-txt', + ], + sorted([r.name for r in got.records]), + ) found = False for record in got.records: @@ -101,11 +88,9 @@ class TestOwnershipProcessor(TestCase): # Something extra exists and doesn't have ownership TXT, leave it # alone, we don't own it. - extra_a = Record.new(zone, 'extra-a', { - 'ttl': 30, - 'type': 'A', - 'value': '4.4.4.4', - }) + extra_a = Record.new( + zone, 'extra-a', {'ttl': 30, 'type': 'A', 'value': '4.4.4.4'} + ) plan.existing.add_record(extra_a) # If we'd done a "real" plan we'd have a delete for the extra thing. plan.changes.append(Delete(extra_a)) @@ -130,11 +115,9 @@ class TestOwnershipProcessor(TestCase): the_a = records['the-a'] plan.existing.add_record(the_a) name = f'{ownership.txt_name}.a.the-a' - the_a_ownership = Record.new(zone, name, { - 'ttl': 30, - 'type': 'TXT', - 'value': ownership.txt_value, - }) + the_a_ownership = Record.new( + zone, name, {'ttl': 30, 'type': 'TXT', 'value': ownership.txt_value} + ) plan.existing.add_record(the_a_ownership) plan.changes.append(Delete(the_a)) plan.changes.append(Delete(the_a_ownership)) diff --git a/tests/test_octodns_provider_azuredns.py b/tests/test_octodns_provider_azuredns.py index 4990ad3..f220756 100644 --- a/tests/test_octodns_provider_azuredns.py +++ b/tests/test_octodns_provider_azuredns.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestAzureShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.azuredns import AzureProvider + AzureProvider diff --git a/tests/test_octodns_provider_base.py b/tests/test_octodns_provider_base.py index e1140c5..b47cc3c 100644 --- a/tests/test_octodns_provider_base.py +++ b/tests/test_octodns_provider_base.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger from unittest import TestCase @@ -27,8 +31,12 @@ class HelperProvider(BaseProvider): id = 'test' strict_supports = False - def __init__(self, extra_changes=[], apply_disabled=False, - include_change_callback=None): + def __init__( + self, + extra_changes=[], + apply_disabled=False, + include_change_callback=None, + ): self.__extra_changes = extra_changes self.apply_disabled = apply_disabled self.include_change_callback = include_change_callback @@ -39,8 +47,9 @@ class HelperProvider(BaseProvider): return True def _include_change(self, change): - return not self.include_change_callback or \ - self.include_change_callback(change) + return not self.include_change_callback or self.include_change_callback( + change + ) def _extra_changes(self, **kwargs): return self.__extra_changes @@ -50,7 +59,6 @@ class HelperProvider(BaseProvider): class TrickyProcessor(BaseProcessor): - def __init__(self, name, add_during_process_target_zone): super(TrickyProcessor, self).__init__(name) self.add_during_process_target_zone = add_during_process_target_zone @@ -73,20 +81,22 @@ class TrickyProcessor(BaseProcessor): class TestBaseProvider(TestCase): - def test_base_provider(self): with self.assertRaises(NotImplementedError) as ctx: BaseProvider('base') - self.assertEqual('Abstract base class, log property missing', - str(ctx.exception)) + self.assertEqual( + 'Abstract base class, log property missing', str(ctx.exception) + ) class HasLog(BaseProvider): log = getLogger('HasLog') with self.assertRaises(NotImplementedError) as ctx: HasLog('haslog') - self.assertEqual('Abstract base class, SUPPORTS_GEO property missing', - str(ctx.exception)) + self.assertEqual( + 'Abstract base class, SUPPORTS_GEO property missing', + str(ctx.exception), + ) class HasSupportsGeo(HasLog): SUPPORTS_GEO = False @@ -94,15 +104,18 @@ class TestBaseProvider(TestCase): zone = Zone('unit.tests.', ['sub']) with self.assertRaises(NotImplementedError) as ctx: HasSupportsGeo('hassupportsgeo').populate(zone) - self.assertEqual('Abstract base class, SUPPORTS property missing', - str(ctx.exception)) + self.assertEqual( + 'Abstract base class, SUPPORTS property missing', str(ctx.exception) + ) class HasSupports(HasSupportsGeo): SUPPORTS = set(('A',)) + with self.assertRaises(NotImplementedError) as ctx: HasSupports('hassupports').populate(zone) - self.assertEqual('Abstract base class, populate method missing', - str(ctx.exception)) + self.assertEqual( + 'Abstract base class, populate method missing', str(ctx.exception) + ) # SUPPORTS_DYNAMIC has a default/fallback self.assertFalse(HasSupports('hassupports').SUPPORTS_DYNAMIC) @@ -111,44 +124,51 @@ class TestBaseProvider(TestCase): class HasSupportsDyanmic(HasSupports): SUPPORTS_DYNAMIC = True - self.assertTrue(HasSupportsDyanmic('hassupportsdynamic') - .SUPPORTS_DYNAMIC) + self.assertTrue( + HasSupportsDyanmic('hassupportsdynamic').SUPPORTS_DYNAMIC + ) class HasPopulate(HasSupports): - def populate(self, zone, target=False, lenient=False): - zone.add_record(Record.new(zone, '', { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - }), lenient=lenient) - zone.add_record(Record.new(zone, 'going', { - 'ttl': 60, - 'type': 'A', - 'value': '3.4.5.6' - }), lenient=lenient) - zone.add_record(Record.new(zone, 'foo.sub', { - 'ttl': 61, - 'type': 'A', - 'value': '4.5.6.7' - }), lenient=lenient) + zone.add_record( + Record.new( + zone, '', {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ), + lenient=lenient, + ) + zone.add_record( + Record.new( + zone, + 'going', + {'ttl': 60, 'type': 'A', 'value': '3.4.5.6'}, + ), + lenient=lenient, + ) + zone.add_record( + Record.new( + zone, + 'foo.sub', + {'ttl': 61, 'type': 'A', 'value': '4.5.6.7'}, + ), + lenient=lenient, + ) - zone.add_record(Record.new(zone, '', { - 'ttl': 60, - 'type': 'A', - 'value': '1.2.3.4' - })) + zone.add_record( + Record.new(zone, '', {'ttl': 60, 'type': 'A', 'value': '1.2.3.4'}) + ) - self.assertTrue(HasSupports('hassupportsgeo') - .supports(list(zone.records)[0])) + self.assertTrue( + HasSupports('hassupportsgeo').supports(list(zone.records)[0]) + ) plan = HasPopulate('haspopulate').plan(zone) self.assertEqual(3, len(plan.changes)) with self.assertRaises(NotImplementedError) as ctx: HasPopulate('haspopulate').apply(plan) - self.assertEqual('Abstract base class, _apply method missing', - str(ctx.exception)) + self.assertEqual( + 'Abstract base class, _apply method missing', str(ctx.exception) + ) def test_plan(self): ignored = Zone('unit.tests.', []) @@ -157,11 +177,9 @@ class TestBaseProvider(TestCase): provider = HelperProvider([]) self.assertEqual(None, provider.plan(ignored)) - record = Record.new(ignored, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + ignored, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) provider = HelperProvider([Create(record)]) plan = provider.plan(ignored) self.assertTrue(plan) @@ -171,7 +189,6 @@ class TestBaseProvider(TestCase): ignored = Zone('unit.tests.', []) class OldApiProvider(HelperProvider): - def _process_desired_zone(self, desired): return desired @@ -180,7 +197,6 @@ class TestBaseProvider(TestCase): self.assertEqual(None, provider.plan(ignored)) class OtherTypeErrorProvider(HelperProvider): - def _process_desired_zone(self, desired, exists=False): raise TypeError('foo') @@ -193,18 +209,20 @@ class TestBaseProvider(TestCase): zone = Zone('unit.tests.', []) # supported - supported = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + supported = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) zone.add_record(supported) # not supported - not_supported = Record.new(zone, 'aaaa', { - 'ttl': 30, - 'type': 'AAAA', - 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', - }) + not_supported = Record.new( + zone, + 'aaaa', + { + 'ttl': 30, + 'type': 'AAAA', + 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', + }, + ) zone.add_record(not_supported) provider = HelperProvider() plan = provider.plan(zone) @@ -215,11 +233,9 @@ class TestBaseProvider(TestCase): def test_plan_with_processors(self): zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) provider = HelperProvider() # Processor that adds a record to the zone, which planning will then # delete since it won't know anything about it @@ -233,11 +249,9 @@ class TestBaseProvider(TestCase): self.assertEqual(zone.name, tricky.existing.name) # Chain of processors happen one after the other - other = Record.new(zone, 'b', { - 'ttl': 30, - 'type': 'A', - 'value': '5.6.7.8', - }) + other = Record.new( + zone, 'b', {'ttl': 30, 'type': 'A', 'value': '5.6.7.8'} + ) # Another processor will add its record, thus 2 deletes another = TrickyProcessor('tricky', [other]) plan = provider.plan(zone, processors=[tricky, another]) @@ -253,11 +267,9 @@ class TestBaseProvider(TestCase): def test_plan_with_root_ns(self): zone = Zone('unit.tests.', []) - record = Record.new(zone, '', { - 'ttl': 30, - 'type': 'NS', - 'value': '1.2.3.4.', - }) + record = Record.new( + zone, '', {'ttl': 30, 'type': 'NS', 'value': '1.2.3.4.'} + ) zone.add_record(record) # No root NS support, no change, thus no plan @@ -273,11 +285,9 @@ class TestBaseProvider(TestCase): def test_apply(self): ignored = Zone('unit.tests.', []) - record = Record.new(ignored, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + ignored, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) provider = HelperProvider([Create(record)], apply_disabled=True) plan = provider.plan(ignored) provider.apply(plan) @@ -288,11 +298,9 @@ class TestBaseProvider(TestCase): def test_include_change(self): zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) zone.add_record(record) provider = HelperProvider([], include_change_callback=lambda c: False) plan = provider.plan(zone) @@ -300,7 +308,6 @@ class TestBaseProvider(TestCase): self.assertFalse(plan) def test_plan_order_of_operations(self): - class MockProvider(BaseProvider): log = getLogger('mock-provider') SUPPORTS = set(('A',)) @@ -327,8 +334,10 @@ class TestBaseProvider(TestCase): self.assertFalse(provider.plan(zone)) # ensure the calls were made in the expected order, populate comes # first, then desired, then existing - self.assertEqual(['populate', '_process_desired_zone', - '_process_existing_zone'], provider.calls) + self.assertEqual( + ['populate', '_process_desired_zone', '_process_existing_zone'], + provider.calls, + ) def test_process_desired_zone(self): provider = HelperProvider('test') @@ -336,11 +345,11 @@ class TestBaseProvider(TestCase): # SUPPORTS_MULTIVALUE_PTR provider.SUPPORTS_MULTIVALUE_PTR = False zone1 = Zone('unit.tests.', []) - record1 = Record.new(zone1, 'ptr', { - 'type': 'PTR', - 'ttl': 3600, - 'values': ['foo.com.', 'bar.com.'], - }) + record1 = Record.new( + zone1, + 'ptr', + {'type': 'PTR', 'ttl': 3600, 'values': ['foo.com.', 'bar.com.']}, + ) zone1.add_record(record1) zone2 = provider._process_desired_zone(zone1.copy()) @@ -355,23 +364,19 @@ class TestBaseProvider(TestCase): # SUPPORTS_DYNAMIC provider.SUPPORTS_DYNAMIC = False zone1 = Zone('unit.tests.', []) - record1 = Record.new(zone1, 'a', { - 'dynamic': { - 'pools': { - 'one': { - 'values': [{ - 'value': '1.1.1.1', - }], - }, + record1 = Record.new( + zone1, + 'a', + { + 'dynamic': { + 'pools': {'one': {'values': [{'value': '1.1.1.1'}]}}, + 'rules': [{'pool': 'one'}], }, - 'rules': [{ - 'pool': 'one', - }], + 'type': 'A', + 'ttl': 3600, + 'values': ['2.2.2.2'], }, - 'type': 'A', - 'ttl': 3600, - 'values': ['2.2.2.2'], - }) + ) self.assertTrue(record1.dynamic) zone1.add_record(record1) @@ -393,18 +398,17 @@ class TestBaseProvider(TestCase): zone2 = provider._process_desired_zone(zone1.copy()) record2 = list(zone2.records)[0] self.assertEqual( - record2.dynamic.pools['one'].data['values'][0]['status'], - 'obey' + record2.dynamic.pools['one'].data['values'][0]['status'], 'obey' ) # SUPPORTS_ROOT_NS provider.SUPPORTS_ROOT_NS = False zone1 = Zone('unit.tests.', []) - record1 = Record.new(zone1, '', { - 'type': 'NS', - 'ttl': 3600, - 'values': ['foo.com.', 'bar.com.'], - }) + record1 = Record.new( + zone1, + '', + {'type': 'NS', 'ttl': 3600, 'values': ['foo.com.', 'bar.com.']}, + ) zone1.add_record(record1) zone2 = provider._process_desired_zone(zone1.copy()) @@ -421,11 +425,11 @@ class TestBaseProvider(TestCase): # SUPPORTS_ROOT_NS provider.SUPPORTS_ROOT_NS = False zone1 = Zone('unit.tests.', []) - record1 = Record.new(zone1, '', { - 'type': 'NS', - 'ttl': 3600, - 'values': ['foo.com.', 'bar.com.'], - }) + record1 = Record.new( + zone1, + '', + {'type': 'NS', 'ttl': 3600, 'values': ['foo.com.', 'bar.com.']}, + ) zone1.add_record(record1) zone2 = provider._process_existing_zone(zone1.copy(), zone1) @@ -444,42 +448,38 @@ class TestBaseProvider(TestCase): # Creates are safe when existing records is under MIN_EXISTING_RECORDS zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) - Plan(zone, zone, [Create(record) for i in range(10)], True) \ - .raise_if_unsafe() + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) + Plan( + zone, zone, [Create(record) for i in range(10)], True + ).raise_if_unsafe() def test_safe_min_existing_creates(self): # Creates are safe when existing records is over MIN_EXISTING_RECORDS zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) - Plan(zone, zone, [Create(record) for i in range(10)], True) \ - .raise_if_unsafe() + Plan( + zone, zone, [Create(record) for i in range(10)], True + ).raise_if_unsafe() def test_safe_no_existing(self): # existing records fewer than MIN_EXISTING_RECORDS is safe zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) updates = [Update(record, record), Update(record, record)] Plan(zone, zone, updates, True).raise_if_unsafe() @@ -488,22 +488,23 @@ class TestBaseProvider(TestCase): # MAX_SAFE_UPDATE_PCENT+1 fails when more # than MIN_EXISTING_RECORDS exist zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) - changes = [Update(record, record) - for i in range(int(Plan.MIN_EXISTING_RECORDS * - Plan.MAX_SAFE_UPDATE_PCENT) + 1)] + changes = [ + Update(record, record) + for i in range( + int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_UPDATE_PCENT) + 1 + ) + ] with self.assertRaises(UnsafePlan) as ctx: Plan(zone, zone, changes, True).raise_if_unsafe() @@ -514,21 +515,22 @@ class TestBaseProvider(TestCase): # MAX_SAFE_UPDATE_PCENT is safe when more # than MIN_EXISTING_RECORDS exist zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) - changes = [Update(record, record) - for i in range(int(Plan.MIN_EXISTING_RECORDS * - Plan.MAX_SAFE_UPDATE_PCENT))] + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) + changes = [ + Update(record, record) + for i in range( + int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_UPDATE_PCENT) + ) + ] Plan(zone, zone, changes, True).raise_if_unsafe() @@ -536,22 +538,23 @@ class TestBaseProvider(TestCase): # MAX_SAFE_DELETE_PCENT+1 fails when more # than MIN_EXISTING_RECORDS exist zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) - changes = [Delete(record) - for i in range(int(Plan.MIN_EXISTING_RECORDS * - Plan.MAX_SAFE_DELETE_PCENT) + 1)] + changes = [ + Delete(record) + for i in range( + int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_DELETE_PCENT) + 1 + ) + ] with self.assertRaises(UnsafePlan) as ctx: Plan(zone, zone, changes, True).raise_if_unsafe() @@ -562,77 +565,78 @@ class TestBaseProvider(TestCase): # MAX_SAFE_DELETE_PCENT is safe when more # than MIN_EXISTING_RECORDS exist zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) - changes = [Delete(record) - for i in range(int(Plan.MIN_EXISTING_RECORDS * - Plan.MAX_SAFE_DELETE_PCENT))] + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) + changes = [ + Delete(record) + for i in range( + int(Plan.MIN_EXISTING_RECORDS * Plan.MAX_SAFE_DELETE_PCENT) + ) + ] Plan(zone, zone, changes, True).raise_if_unsafe() def test_safe_updates_min_existing_override(self): - safe_pcent = .4 + safe_pcent = 0.4 # 40% + 1 fails when more # than MIN_EXISTING_RECORDS exist zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) - changes = [Update(record, record) - for i in range(int(Plan.MIN_EXISTING_RECORDS * - safe_pcent) + 1)] + changes = [ + Update(record, record) + for i in range(int(Plan.MIN_EXISTING_RECORDS * safe_pcent) + 1) + ] with self.assertRaises(UnsafePlan) as ctx: - Plan(zone, zone, changes, True, - update_pcent_threshold=safe_pcent).raise_if_unsafe() + Plan( + zone, zone, changes, True, update_pcent_threshold=safe_pcent + ).raise_if_unsafe() self.assertTrue('Too many updates' in str(ctx.exception)) def test_safe_deletes_min_existing_override(self): - safe_pcent = .4 + safe_pcent = 0.4 # 40% + 1 fails when more # than MIN_EXISTING_RECORDS exist zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) for i in range(int(Plan.MIN_EXISTING_RECORDS)): - zone.add_record(Record.new(zone, str(i), { - 'ttl': 60, - 'type': 'A', - 'value': '2.3.4.5' - })) + zone.add_record( + Record.new( + zone, str(i), {'ttl': 60, 'type': 'A', 'value': '2.3.4.5'} + ) + ) - changes = [Delete(record) - for i in range(int(Plan.MIN_EXISTING_RECORDS * - safe_pcent) + 1)] + changes = [ + Delete(record) + for i in range(int(Plan.MIN_EXISTING_RECORDS * safe_pcent) + 1) + ] with self.assertRaises(UnsafePlan) as ctx: - Plan(zone, zone, changes, True, - delete_pcent_threshold=safe_pcent).raise_if_unsafe() + Plan( + zone, zone, changes, True, delete_pcent_threshold=safe_pcent + ).raise_if_unsafe() self.assertTrue('Too many deletes' in str(ctx.exception)) @@ -649,9 +653,9 @@ class TestBaseProvider(TestCase): # Should log and not expect normal.supports_warn_or_except('Hello World!', 'Goodbye') normal.log.warning.assert_called_once() - normal.log.warning.assert_has_calls([ - call('%s; %s', 'Hello World!', 'Goodbye') - ]) + normal.log.warning.assert_has_calls( + [call('%s; %s', 'Hello World!', 'Goodbye')] + ) strict = MinimalProvider(strict_supports=True) # Should log and not expect @@ -662,7 +666,6 @@ class TestBaseProvider(TestCase): class TestBaseProviderSupportsRootNs(TestCase): - class Provider(BaseProvider): log = getLogger('Provider') @@ -684,33 +687,33 @@ class TestBaseProviderSupportsRootNs(TestCase): return False zone = Zone('unit.tests.', []) - a_record = Record.new(zone, 'ptr', { - 'type': 'A', - 'ttl': 3600, - 'values': ['1.2.3.4', '2.3.4.5'], - }) - ns_record = Record.new(zone, 'sub', { - 'type': 'NS', - 'ttl': 3600, - 'values': ['ns2.foo.com.', 'ns2.bar.com.'], - }) + a_record = Record.new( + zone, + 'ptr', + {'type': 'A', 'ttl': 3600, 'values': ['1.2.3.4', '2.3.4.5']}, + ) + ns_record = Record.new( + zone, + 'sub', + {'type': 'NS', 'ttl': 3600, 'values': ['ns2.foo.com.', 'ns2.bar.com.']}, + ) no_root = zone.copy() no_root.add_record(a_record) no_root.add_record(ns_record) - root_ns_record = Record.new(zone, '', { - 'type': 'NS', - 'ttl': 3600, - 'values': ['ns1.foo.com.', 'ns1.bar.com.'], - }) + root_ns_record = Record.new( + zone, + '', + {'type': 'NS', 'ttl': 3600, 'values': ['ns1.foo.com.', 'ns1.bar.com.']}, + ) has_root = no_root.copy() has_root.add_record(root_ns_record) - other_root_ns_record = Record.new(zone, '', { - 'type': 'NS', - 'ttl': 3600, - 'values': ['ns4.foo.com.', 'ns4.bar.com.'], - }) + other_root_ns_record = Record.new( + zone, + '', + {'type': 'NS', 'ttl': 3600, 'values': ['ns4.foo.com.', 'ns4.bar.com.']}, + ) different_root = no_root.copy() different_root.add_record(other_root_ns_record) @@ -733,8 +736,10 @@ class TestBaseProviderSupportsRootNs(TestCase): provider.strict_supports = True with self.assertRaises(SupportsException) as ctx: provider.plan(self.has_root) - self.assertEqual('test: root NS record not supported for unit.tests.', - str(ctx.exception)) + self.assertEqual( + 'test: root NS record not supported for unit.tests.', + str(ctx.exception), + ) def test_supports_root_ns_false_different(self): # provider has a non-matching existing record @@ -754,8 +759,10 @@ class TestBaseProviderSupportsRootNs(TestCase): provider.strict_supports = True with self.assertRaises(SupportsException) as ctx: provider.plan(self.has_root) - self.assertEqual('test: root NS record not supported for unit.tests.', - str(ctx.exception)) + self.assertEqual( + 'test: root NS record not supported for unit.tests.', + str(ctx.exception), + ) def test_supports_root_ns_false_missing(self): # provider has an existing record @@ -792,8 +799,10 @@ class TestBaseProviderSupportsRootNs(TestCase): provider.strict_supports = True with self.assertRaises(SupportsException) as ctx: provider.plan(self.has_root) - self.assertEqual('test: root NS record not supported for unit.tests.', - str(ctx.exception)) + self.assertEqual( + 'test: root NS record not supported for unit.tests.', + str(ctx.exception), + ) def test_supports_root_ns_false_create_zone_missing(self): # provider has no existing records (create) @@ -889,8 +898,9 @@ class TestBaseProviderSupportsRootNs(TestCase): # we'll get a plan that creates everything, including it self.assertTrue(plan) self.assertEqual(3, len(plan.changes)) - change = [c for c in plan.changes - if c.new.name == '' and c.new._type == 'NS'][0] + change = [ + c for c in plan.changes if c.new.name == '' and c.new._type == 'NS' + ][0] self.assertFalse(change.existing) self.assertEqual(self.root_ns_record, change.new) @@ -900,8 +910,9 @@ class TestBaseProviderSupportsRootNs(TestCase): plan = provider.plan(self.has_root) self.assertTrue(plan) self.assertEqual(3, len(plan.changes)) - change = [c for c in plan.changes - if c.new.name == '' and c.new._type == 'NS'][0] + change = [ + c for c in plan.changes if c.new.name == '' and c.new._type == 'NS' + ][0] self.assertFalse(change.existing) self.assertEqual(self.root_ns_record, change.new) diff --git a/tests/test_octodns_provider_cloudflare.py b/tests/test_octodns_provider_cloudflare.py index 1053b7d..3b5f1ff 100644 --- a/tests/test_octodns_provider_cloudflare.py +++ b/tests/test_octodns_provider_cloudflare.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestCloudflareShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.cloudflare import CloudflareProvider + CloudflareProvider diff --git a/tests/test_octodns_provider_constellix.py b/tests/test_octodns_provider_constellix.py index 46e87ce..97528d2 100644 --- a/tests/test_octodns_provider_constellix.py +++ b/tests/test_octodns_provider_constellix.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestConstellixShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.constellix import ConstellixProvider + ConstellixProvider diff --git a/tests/test_octodns_provider_digitalocean.py b/tests/test_octodns_provider_digitalocean.py index 56ca965..0b36a4c 100644 --- a/tests/test_octodns_provider_digitalocean.py +++ b/tests/test_octodns_provider_digitalocean.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestDigitalOceanShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.digitalocean import DigitalOceanProvider + DigitalOceanProvider diff --git a/tests/test_octodns_provider_dnsimple.py b/tests/test_octodns_provider_dnsimple.py index 611caea..33a2430 100644 --- a/tests/test_octodns_provider_dnsimple.py +++ b/tests/test_octodns_provider_dnsimple.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestDnsimpleShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.dnsimple import DnsimpleProvider + DnsimpleProvider diff --git a/tests/test_octodns_provider_dnsmadeeasy.py b/tests/test_octodns_provider_dnsmadeeasy.py index 968ae30..18fa968 100644 --- a/tests/test_octodns_provider_dnsmadeeasy.py +++ b/tests/test_octodns_provider_dnsmadeeasy.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestDnsMadeEasyShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.dnsmadeeasy import DnsMadeEasyProvider + DnsMadeEasyProvider diff --git a/tests/test_octodns_provider_dyn.py b/tests/test_octodns_provider_dyn.py index 07fcc7b..ad5735f 100644 --- a/tests/test_octodns_provider_dyn.py +++ b/tests/test_octodns_provider_dyn.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestDynShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.dyn import DynProvider + DynProvider diff --git a/tests/test_octodns_provider_easydns.py b/tests/test_octodns_provider_easydns.py index b92e68e..151a6d7 100644 --- a/tests/test_octodns_provider_easydns.py +++ b/tests/test_octodns_provider_easydns.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestEasyDnsShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.easydns import EasyDnsProvider + EasyDnsProvider diff --git a/tests/test_octodns_provider_edgedns.py b/tests/test_octodns_provider_edgedns.py index d1ba0d3..1df1fdc 100644 --- a/tests/test_octodns_provider_edgedns.py +++ b/tests/test_octodns_provider_edgedns.py @@ -2,20 +2,25 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase # Just for coverage import octodns.provider.fastdns + # Quell warnings octodns.provider.fastdns class TestAkamaiShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.edgedns import AkamaiProvider + AkamaiProvider diff --git a/tests/test_octodns_provider_etc_hosts.py b/tests/test_octodns_provider_etc_hosts.py index afefc0e..9a08106 100644 --- a/tests/test_octodns_provider_etc_hosts.py +++ b/tests/test_octodns_provider_etc_hosts.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestEtcHostsShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.etc_hosts import EtcHostsProvider + EtcHostsProvider diff --git a/tests/test_octodns_provider_gandi.py b/tests/test_octodns_provider_gandi.py index 1e3df1e..00f2c30 100644 --- a/tests/test_octodns_provider_gandi.py +++ b/tests/test_octodns_provider_gandi.py @@ -2,20 +2,25 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase # Just for coverage import octodns.provider.fastdns + # Quell warnings octodns.provider.fastdns class TestGandiShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.gandi import GandiProvider + GandiProvider diff --git a/tests/test_octodns_provider_gcore.py b/tests/test_octodns_provider_gcore.py index 0f40c8b..dd54268 100644 --- a/tests/test_octodns_provider_gcore.py +++ b/tests/test_octodns_provider_gcore.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestGCoreShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.gcore import GCoreProvider + GCoreProvider diff --git a/tests/test_octodns_provider_googlecloud.py b/tests/test_octodns_provider_googlecloud.py index 4a8fdf5..5188cd8 100644 --- a/tests/test_octodns_provider_googlecloud.py +++ b/tests/test_octodns_provider_googlecloud.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestGoogleCloudShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.googlecloud import GoogleCloudProvider + GoogleCloudProvider diff --git a/tests/test_octodns_provider_hetzner.py b/tests/test_octodns_provider_hetzner.py index 0976157..7f28345 100644 --- a/tests/test_octodns_provider_hetzner.py +++ b/tests/test_octodns_provider_hetzner.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestHetznerShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.hetzner import HetznerProvider + HetznerProvider diff --git a/tests/test_octodns_provider_mythicbeasts.py b/tests/test_octodns_provider_mythicbeasts.py index f8c316c..7ae74b8 100644 --- a/tests/test_octodns_provider_mythicbeasts.py +++ b/tests/test_octodns_provider_mythicbeasts.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestMythicBeastsShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.mythicbeasts import MythicBeastsProvider + MythicBeastsProvider diff --git a/tests/test_octodns_provider_ns1.py b/tests/test_octodns_provider_ns1.py index 3d85dd0..09ab1e3 100644 --- a/tests/test_octodns_provider_ns1.py +++ b/tests/test_octodns_provider_ns1.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestNs1Provider(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.ns1 import Ns1Provider + Ns1Provider diff --git a/tests/test_octodns_provider_ovh.py b/tests/test_octodns_provider_ovh.py index 27ac8f6..8973af7 100644 --- a/tests/test_octodns_provider_ovh.py +++ b/tests/test_octodns_provider_ovh.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestOvhShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.ovh import OvhProvider + OvhProvider diff --git a/tests/test_octodns_provider_powerdns.py b/tests/test_octodns_provider_powerdns.py index 464efef..9698c8c 100644 --- a/tests/test_octodns_provider_powerdns.py +++ b/tests/test_octodns_provider_powerdns.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestPowerDnsShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.powerdns import PowerDnsProvider + PowerDnsProvider diff --git a/tests/test_octodns_provider_rackspace.py b/tests/test_octodns_provider_rackspace.py index 971c450..cb30d56 100644 --- a/tests/test_octodns_provider_rackspace.py +++ b/tests/test_octodns_provider_rackspace.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestRackspaceShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.rackspace import RackspaceProvider + RackspaceProvider diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index 23e5748..2486adc 100644 --- a/tests/test_octodns_provider_route53.py +++ b/tests/test_octodns_provider_route53.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestRoute53Provider(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.route53 import Route53Provider + Route53Provider diff --git a/tests/test_octodns_provider_selectel.py b/tests/test_octodns_provider_selectel.py index 6a0a37a..5c429d9 100644 --- a/tests/test_octodns_provider_selectel.py +++ b/tests/test_octodns_provider_selectel.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestSelectelShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.selectel import SelectelProvider + SelectelProvider diff --git a/tests/test_octodns_provider_transip.py b/tests/test_octodns_provider_transip.py index faa90b3..eddddb0 100644 --- a/tests/test_octodns_provider_transip.py +++ b/tests/test_octodns_provider_transip.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestTransipShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.transip import TransipProvider + TransipProvider diff --git a/tests/test_octodns_provider_ultra.py b/tests/test_octodns_provider_ultra.py index acf2805..0e6d9f2 100644 --- a/tests/test_octodns_provider_ultra.py +++ b/tests/test_octodns_provider_ultra.py @@ -2,15 +2,19 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase class TestUltraShim(TestCase): - def test_missing(self): with self.assertRaises(ModuleNotFoundError): from octodns.provider.ultra import UltraProvider + UltraProvider diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index 3a6ead8..f332eae 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from os import makedirs from os.path import basename, dirname, isdir, isfile, join @@ -13,15 +17,17 @@ from yaml.constructor import ConstructorError from octodns.record import Create from octodns.provider.base import Plan -from octodns.provider.yaml import _list_all_yaml_files, \ - SplitYamlProvider, YamlProvider +from octodns.provider.yaml import ( + _list_all_yaml_files, + SplitYamlProvider, + YamlProvider, +) from octodns.zone import SubzoneRecordException, Zone from helpers import TemporaryDirectory class TestYamlProvider(TestCase): - def test_provider(self): source = YamlProvider('test', join(dirname(__file__), 'config')) @@ -57,8 +63,9 @@ class TestYamlProvider(TestCase): # We add everything plan = target.plan(zone) - self.assertEqual(22, len([c for c in plan.changes - if isinstance(c, Create)])) + self.assertEqual( + 22, len([c for c in plan.changes if isinstance(c, Create)]) + ) self.assertFalse(isfile(yaml_file)) # Now actually do it @@ -67,8 +74,9 @@ class TestYamlProvider(TestCase): # Dynamic plan plan = target.plan(dynamic_zone) - self.assertEqual(6, len([c for c in plan.changes - if isinstance(c, Create)])) + self.assertEqual( + 6, len([c for c in plan.changes if isinstance(c, Create)]) + ) self.assertFalse(isfile(dynamic_yaml_file)) # Apply it self.assertEqual(6, target.apply(plan)) @@ -79,8 +87,10 @@ class TestYamlProvider(TestCase): target.populate(reloaded) self.assertDictEqual( {'included': ['test']}, - [x for x in reloaded.records - if x.name == 'included'][0]._octodns) + [x for x in reloaded.records if x.name == 'included'][ + 0 + ]._octodns, + ) # manually copy over the root since it will have been ignored # when things were written out @@ -90,8 +100,9 @@ class TestYamlProvider(TestCase): # A 2nd sync should still create everything plan = target.plan(zone) - self.assertEqual(22, len([c for c in plan.changes - if isinstance(c, Create)])) + self.assertEqual( + 22, len([c for c in plan.changes if isinstance(c, Create)]) + ) with open(yaml_file) as fh: data = safe_load(fh.read()) @@ -100,7 +111,7 @@ class TestYamlProvider(TestCase): roots = sorted(data.pop(''), key=lambda r: r['type']) self.assertTrue('values' in roots[0]) # A self.assertTrue('geo' in roots[0]) # geo made the trip - self.assertTrue('value' in roots[1]) # CAA + self.assertTrue('value' in roots[1]) # CAA self.assertTrue('values' in roots[2]) # SSHFP # these are stored as plural 'values' @@ -162,8 +173,9 @@ class TestYamlProvider(TestCase): self.assertEqual([], list(data.keys())) def test_empty(self): - source = YamlProvider('test', join(dirname(__file__), 'config'), - supports_root_ns=False) + source = YamlProvider( + 'test', join(dirname(__file__), 'config'), supports_root_ns=False + ) zone = Zone('empty.', []) @@ -172,34 +184,41 @@ class TestYamlProvider(TestCase): self.assertEqual(0, len(zone.records)) def test_unsorted(self): - source = YamlProvider('test', join(dirname(__file__), 'config'), - supports_root_ns=False) + source = YamlProvider( + 'test', join(dirname(__file__), 'config'), supports_root_ns=False + ) zone = Zone('unordered.', []) with self.assertRaises(ConstructorError): source.populate(zone) - source = YamlProvider('test', join(dirname(__file__), 'config'), - enforce_order=False, supports_root_ns=False) + source = YamlProvider( + 'test', + join(dirname(__file__), 'config'), + enforce_order=False, + supports_root_ns=False, + ) # no exception source.populate(zone) self.assertEqual(2, len(zone.records)) def test_subzone_handling(self): - source = YamlProvider('test', join(dirname(__file__), 'config'), - supports_root_ns=False) + source = YamlProvider( + 'test', join(dirname(__file__), 'config'), supports_root_ns=False + ) # If we add `sub` as a sub-zone we'll reject `www.sub` 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)) + self.assertEqual( + 'Record www.sub.unit.tests. is under a managed ' 'subzone', + str(ctx.exception), + ) class TestSplitYamlProvider(TestCase): - def test_list_all_yaml_files(self): yaml_files = ('foo.yaml', '1.yaml', '$unit.tests.yaml') all_files = ('something', 'else', '1', '$$', '-f') + yaml_files @@ -223,19 +242,21 @@ class TestSplitYamlProvider(TestCase): def test_zone_directory(self): source = SplitYamlProvider( - 'test', join(dirname(__file__), 'config/split'), - extension='.tst') + 'test', join(dirname(__file__), 'config/split'), extension='.tst' + ) zone = Zone('unit.tests.', []) self.assertEqual( join(dirname(__file__), 'config/split', 'unit.tests.tst'), - source._zone_directory(zone)) + source._zone_directory(zone), + ) def test_apply_handles_existing_zone_directory(self): with TemporaryDirectory() as td: - provider = SplitYamlProvider('test', join(td.dirname, 'config'), - extension='.tst') + provider = SplitYamlProvider( + 'test', join(td.dirname, 'config'), extension='.tst' + ) makedirs(join(td.dirname, 'config', 'does.exist.tst')) zone = Zone('does.exist.', []) @@ -245,8 +266,8 @@ class TestSplitYamlProvider(TestCase): def test_provider(self): source = SplitYamlProvider( - 'test', join(dirname(__file__), 'config/split'), - extension='.tst') + 'test', join(dirname(__file__), 'config/split'), extension='.tst' + ) zone = Zone('unit.tests.', []) dynamic_zone = Zone('dynamic.tests.', []) @@ -267,14 +288,15 @@ class TestSplitYamlProvider(TestCase): directory = join(td.dirname, 'sub', 'dir') zone_dir = join(directory, 'unit.tests.tst') dynamic_zone_dir = join(directory, 'dynamic.tests.tst') - target = SplitYamlProvider('test', directory, - extension='.tst', - supports_root_ns=False) + target = SplitYamlProvider( + 'test', directory, extension='.tst', supports_root_ns=False + ) # We add everything plan = target.plan(zone) - self.assertEqual(17, len([c for c in plan.changes - if isinstance(c, Create)])) + self.assertEqual( + 17, len([c for c in plan.changes if isinstance(c, Create)]) + ) self.assertFalse(isdir(zone_dir)) # Now actually do it @@ -282,8 +304,9 @@ class TestSplitYamlProvider(TestCase): # Dynamic plan plan = target.plan(dynamic_zone) - self.assertEqual(5, len([c for c in plan.changes - if isinstance(c, Create)])) + self.assertEqual( + 5, len([c for c in plan.changes if isinstance(c, Create)]) + ) self.assertFalse(isdir(dynamic_zone_dir)) # Apply it self.assertEqual(5, target.apply(plan)) @@ -294,8 +317,10 @@ class TestSplitYamlProvider(TestCase): target.populate(reloaded) self.assertDictEqual( {'included': ['test']}, - [x for x in reloaded.records - if x.name == 'included'][0]._octodns) + [x for x in reloaded.records if x.name == 'included'][ + 0 + ]._octodns, + ) # manually copy over the root since it will have been ignored # when things were written out @@ -305,8 +330,9 @@ class TestSplitYamlProvider(TestCase): # A 2nd sync should still create everything plan = target.plan(zone) - self.assertEqual(17, len([c for c in plan.changes - if isinstance(c, Create)])) + self.assertEqual( + 17, len([c for c in plan.changes if isinstance(c, Create)]) + ) yaml_file = join(zone_dir, '$unit.tests.yaml') self.assertTrue(isfile(yaml_file)) @@ -315,13 +341,19 @@ class TestSplitYamlProvider(TestCase): roots = sorted(data.pop(''), key=lambda r: r['type']) self.assertTrue('values' in roots[0]) # A self.assertTrue('geo' in roots[0]) # geo made the trip - self.assertTrue('value' in roots[1]) # CAA + self.assertTrue('value' in roots[1]) # CAA self.assertTrue('values' in roots[2]) # SSHFP # These records are stored as plural "values." Check each file to # ensure correctness. - for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt', - 'urlfwd'): + for record_name in ( + '_srv._tcp', + 'mx', + 'naptr', + 'sub', + 'txt', + 'urlfwd', + ): yaml_file = join(zone_dir, f'{record_name}.yaml') self.assertTrue(isfile(yaml_file)) with open(yaml_file) as fh: @@ -329,8 +361,16 @@ class TestSplitYamlProvider(TestCase): self.assertTrue('values' in data.pop(record_name)) # These are stored as singular "value." Again, check each file. - for record_name in ('aaaa', 'cname', 'dname', 'included', 'ptr', - 'spf', 'www.sub', 'www'): + for record_name in ( + 'aaaa', + 'cname', + 'dname', + 'included', + 'ptr', + 'spf', + 'www.sub', + 'www', + ): yaml_file = join(zone_dir, f'{record_name}.yaml') self.assertTrue(isfile(yaml_file)) with open(yaml_file) as fh: @@ -339,8 +379,7 @@ class TestSplitYamlProvider(TestCase): # Again with the plural, this time checking dynamic.tests. for record_name in ('a', 'aaaa', 'real-ish-a'): - yaml_file = join( - dynamic_zone_dir, f'{record_name}.yaml') + yaml_file = join(dynamic_zone_dir, f'{record_name}.yaml') self.assertTrue(isfile(yaml_file)) with open(yaml_file) as fh: data = safe_load(fh.read()) @@ -360,8 +399,8 @@ class TestSplitYamlProvider(TestCase): def test_empty(self): source = SplitYamlProvider( - 'test', join(dirname(__file__), 'config/split'), - extension='.tst') + 'test', join(dirname(__file__), 'config/split'), extension='.tst' + ) zone = Zone('empty.', []) @@ -371,8 +410,8 @@ class TestSplitYamlProvider(TestCase): def test_unsorted(self): source = SplitYamlProvider( - 'test', join(dirname(__file__), 'config/split'), - extension='.tst') + 'test', join(dirname(__file__), 'config/split'), extension='.tst' + ) zone = Zone('unordered.', []) @@ -382,35 +421,46 @@ class TestSplitYamlProvider(TestCase): zone = Zone('unordered.', []) source = SplitYamlProvider( - 'test', join(dirname(__file__), 'config/split'), - extension='.tst', enforce_order=False) + 'test', + join(dirname(__file__), 'config/split'), + extension='.tst', + enforce_order=False, + ) # no exception source.populate(zone) self.assertEqual(2, len(zone.records)) def test_subzone_handling(self): source = SplitYamlProvider( - 'test', join(dirname(__file__), 'config/split'), - extension='.tst') + 'test', join(dirname(__file__), 'config/split'), extension='.tst' + ) # If we add `sub` as a sub-zone we'll reject `www.sub` 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)) + self.assertEqual( + 'Record www.sub.unit.tests. is under a managed ' 'subzone', + str(ctx.exception), + ) class TestOverridingYamlProvider(TestCase): - def test_provider(self): config = join(dirname(__file__), 'config') override_config = join(dirname(__file__), 'config', 'override') - base = YamlProvider('base', config, populate_should_replace=False, - supports_root_ns=False) - override = YamlProvider('test', override_config, - populate_should_replace=True, - supports_root_ns=False) + base = YamlProvider( + 'base', + config, + populate_should_replace=False, + supports_root_ns=False, + ) + override = YamlProvider( + 'test', + override_config, + populate_should_replace=True, + supports_root_ns=False, + ) zone = Zone('dynamic.tests.', []) @@ -428,9 +478,8 @@ class TestOverridingYamlProvider(TestCase): got = {r.name: r for r in zone.records} self.assertEqual(7, len(got)) # 'a' was replaced with a generic record - self.assertEqual({ - 'ttl': 3600, - 'values': ['4.4.4.4', '5.5.5.5'] - }, got['a'].data) + self.assertEqual( + {'ttl': 3600, 'values': ['4.4.4.4', '5.5.5.5']}, got['a'].data + ) # And we have the new one self.assertTrue('added' in got) diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 4649745..2bbea5d 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -2,18 +2,53 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase -from octodns.record import ARecord, AaaaRecord, AliasRecord, CaaRecord, \ - CaaValue, CnameRecord, DnameRecord, Create, Delete, GeoValue, LocRecord, \ - LocValue, MxRecord, MxValue, NaptrRecord, NaptrValue, NsRecord, \ - PtrRecord, Record, RecordException, SshfpRecord, SshfpValue, SpfRecord, \ - SrvRecord, SrvValue, TlsaRecord, TxtRecord, Update, UrlfwdRecord, \ - UrlfwdValue, ValidationError, _Dynamic, _DynamicPool, _DynamicRule, \ - _NsValue, ValuesMixin +from octodns.record import ( + ARecord, + AaaaRecord, + AliasRecord, + CaaRecord, + CaaValue, + CnameRecord, + DnameRecord, + Create, + Delete, + GeoValue, + LocRecord, + LocValue, + MxRecord, + MxValue, + NaptrRecord, + NaptrValue, + NsRecord, + PtrRecord, + Record, + RecordException, + SshfpRecord, + SshfpValue, + SpfRecord, + SrvRecord, + SrvValue, + TlsaRecord, + TxtRecord, + Update, + UrlfwdRecord, + UrlfwdValue, + ValidationError, + _Dynamic, + _DynamicPool, + _DynamicRule, + _NsValue, + ValuesMixin, +) from octodns.zone import Zone from helpers import DynamicProvider, GeoProvider, SimpleProvider @@ -25,79 +60,79 @@ class TestRecord(TestCase): def test_registration(self): with self.assertRaises(RecordException) as ctx: Record.register_type(None, 'A') - self.assertEqual('Type "A" already registered by ' - 'octodns.record.ARecord', str(ctx.exception)) + self.assertEqual( + 'Type "A" already registered by ' 'octodns.record.ARecord', + str(ctx.exception), + ) class AaRecord(ValuesMixin, Record): _type = 'AA' _value_type = _NsValue Record.register_type(AaRecord) - aa = Record.new(self.zone, 'registered', { - 'ttl': 360, - 'type': 'AA', - 'value': 'does.not.matter.', - }) + aa = Record.new( + self.zone, + 'registered', + {'ttl': 360, 'type': 'AA', 'value': 'does.not.matter.'}, + ) self.assertEqual(AaRecord, aa.__class__) def test_lowering(self): - record = ARecord(self.zone, 'MiXeDcAsE', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = ARecord( + self.zone, 'MiXeDcAsE', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) self.assertEqual('mixedcase', record.name) def test_alias_lowering_value(self): - upper_record = AliasRecord(self.zone, 'aliasUppwerValue', { - 'ttl': 30, - 'type': 'ALIAS', - 'value': 'GITHUB.COM', - }) - lower_record = AliasRecord(self.zone, 'aliasLowerValue', { - 'ttl': 30, - 'type': 'ALIAS', - 'value': 'github.com', - }) + upper_record = AliasRecord( + self.zone, + 'aliasUppwerValue', + {'ttl': 30, 'type': 'ALIAS', 'value': 'GITHUB.COM'}, + ) + lower_record = AliasRecord( + self.zone, + 'aliasLowerValue', + {'ttl': 30, 'type': 'ALIAS', 'value': 'github.com'}, + ) self.assertEqual(upper_record.value, lower_record.value) def test_cname_lowering_value(self): - upper_record = CnameRecord(self.zone, 'CnameUppwerValue', { - 'ttl': 30, - 'type': 'CNAME', - 'value': 'GITHUB.COM', - }) - lower_record = CnameRecord(self.zone, 'CnameLowerValue', { - 'ttl': 30, - 'type': 'CNAME', - 'value': 'github.com', - }) + upper_record = CnameRecord( + self.zone, + 'CnameUppwerValue', + {'ttl': 30, 'type': 'CNAME', 'value': 'GITHUB.COM'}, + ) + lower_record = CnameRecord( + self.zone, + 'CnameLowerValue', + {'ttl': 30, 'type': 'CNAME', 'value': 'github.com'}, + ) self.assertEqual(upper_record.value, lower_record.value) def test_dname_lowering_value(self): - upper_record = DnameRecord(self.zone, 'DnameUppwerValue', { - 'ttl': 30, - 'type': 'DNAME', - 'value': 'GITHUB.COM', - }) - lower_record = DnameRecord(self.zone, 'DnameLowerValue', { - 'ttl': 30, - 'type': 'DNAME', - 'value': 'github.com', - }) + upper_record = DnameRecord( + self.zone, + 'DnameUppwerValue', + {'ttl': 30, 'type': 'DNAME', 'value': 'GITHUB.COM'}, + ) + lower_record = DnameRecord( + self.zone, + 'DnameLowerValue', + {'ttl': 30, 'type': 'DNAME', 'value': 'github.com'}, + ) self.assertEqual(upper_record.value, lower_record.value) def test_ptr_lowering_value(self): - upper_record = PtrRecord(self.zone, 'PtrUppwerValue', { - 'ttl': 30, - 'type': 'PTR', - 'value': 'GITHUB.COM', - }) - lower_record = PtrRecord(self.zone, 'PtrLowerValue', { - 'ttl': 30, - 'type': 'PTR', - 'value': 'github.com', - }) + upper_record = PtrRecord( + self.zone, + 'PtrUppwerValue', + {'ttl': 30, 'type': 'PTR', 'value': 'GITHUB.COM'}, + ) + lower_record = PtrRecord( + self.zone, + 'PtrLowerValue', + {'ttl': 30, 'type': 'PTR', 'value': 'github.com'}, + ) self.assertEqual(upper_record.value, lower_record.value) def test_a_and_record(self): @@ -126,11 +161,13 @@ class TestRecord(TestCase): # Records with differing names and same type don't equate self.assertFalse(a == b) # Records with same name & type equate even if ttl is different - self.assertTrue(a == ARecord(self.zone, 'a', - {'ttl': 31, 'values': a_values})) + self.assertTrue( + a == ARecord(self.zone, 'a', {'ttl': 31, 'values': a_values}) + ) # Records with same name & type equate even if values are different - self.assertTrue(a == ARecord(self.zone, 'a', - {'ttl': 30, 'value': b_value})) + self.assertTrue( + a == ARecord(self.zone, 'a', {'ttl': 30, 'value': b_value}) + ) target = SimpleProvider() # no changes if self @@ -162,8 +199,8 @@ class TestRecord(TestCase): a.__repr__() # Record.__repr__ does with self.assertRaises(NotImplementedError): - class DummyRecord(Record): + class DummyRecord(Record): def __init__(self): pass @@ -171,69 +208,65 @@ class TestRecord(TestCase): def test_values_mixin_data(self): # no values, no value or values in data - a = ARecord(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': [] - }) + a = ARecord(self.zone, '', {'type': 'A', 'ttl': 600, 'values': []}) self.assertNotIn('values', a.data) # empty value, no value or values in data - b = ARecord(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': [''] - }) + b = ARecord(self.zone, '', {'type': 'A', 'ttl': 600, 'values': ['']}) self.assertNotIn('value', b.data) # empty/None values, no value or values in data - c = ARecord(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': ['', None] - }) + c = ARecord( + self.zone, '', {'type': 'A', 'ttl': 600, 'values': ['', None]} + ) self.assertNotIn('values', c.data) # empty/None values and valid, value in data - c = ARecord(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': ['', None, '10.10.10.10'] - }) + c = ARecord( + self.zone, + '', + {'type': 'A', 'ttl': 600, 'values': ['', None, '10.10.10.10']}, + ) self.assertNotIn('values', c.data) self.assertEqual('10.10.10.10', c.data['value']) def test_value_mixin_data(self): # unspecified value, no value in data - a = AliasRecord(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': None - }) + a = AliasRecord( + self.zone, '', {'type': 'ALIAS', 'ttl': 600, 'value': None} + ) self.assertNotIn('value', a.data) # unspecified value, no value in data - a = AliasRecord(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': '' - }) + a = AliasRecord( + self.zone, '', {'type': 'ALIAS', 'ttl': 600, 'value': ''} + ) self.assertNotIn('value', a.data) def test_geo(self): - geo_data = {'ttl': 42, 'values': ['5.2.3.4', '6.2.3.4'], - 'geo': {'AF': ['1.1.1.1'], - 'AS-JP': ['2.2.2.2', '3.3.3.3'], - 'NA-US': ['4.4.4.4', '5.5.5.5'], - 'NA-US-CA': ['6.6.6.6', '7.7.7.7']}} + geo_data = { + 'ttl': 42, + 'values': ['5.2.3.4', '6.2.3.4'], + 'geo': { + 'AF': ['1.1.1.1'], + 'AS-JP': ['2.2.2.2', '3.3.3.3'], + 'NA-US': ['4.4.4.4', '5.5.5.5'], + 'NA-US-CA': ['6.6.6.6', '7.7.7.7'], + }, + } geo = ARecord(self.zone, 'geo', geo_data) self.assertEqual(geo_data, geo.data) - other_data = {'ttl': 42, 'values': ['5.2.3.4', '6.2.3.4'], - 'geo': {'AF': ['1.1.1.1'], - 'AS-JP': ['2.2.2.2', '3.3.3.3'], - 'NA-US': ['4.4.4.4', '5.5.5.5'], - 'NA-US-CA': ['6.6.6.6', '7.7.7.7']}} + other_data = { + 'ttl': 42, + 'values': ['5.2.3.4', '6.2.3.4'], + 'geo': { + 'AF': ['1.1.1.1'], + 'AS-JP': ['2.2.2.2', '3.3.3.3'], + 'NA-US': ['4.4.4.4', '5.5.5.5'], + 'NA-US-CA': ['6.6.6.6', '7.7.7.7'], + }, + } other = ARecord(self.zone, 'geo', other_data) self.assertEqual(other_data, other.data) @@ -277,18 +310,19 @@ class TestRecord(TestCase): self.assertEqual(b_data, b.data) def test_aaaa(self): - a_values = ['2001:db8:3c4d:15::1a2f:1a2b', - '2001:db8:3c4d:15::1a2f:1a3b'] + a_values = [ + '2001:db8:3c4d:15::1a2f:1a2b', + '2001:db8:3c4d:15::1a2f:1a3b', + ] b_value = '2001:db8:3c4d:15::1a2f:1a4b' self.assertMultipleValues(AaaaRecord, a_values, b_value) # Specifically validate that we normalize IPv6 addresses - values = ['2001:db8:3c4d:15:0000:0000:1a2f:1a2b', - '2001:0db8:3c4d:0015::1a2f:1a3b'] - data = { - 'ttl': 30, - 'values': values, - } + values = [ + '2001:db8:3c4d:15:0000:0000:1a2f:1a2b', + '2001:0db8:3c4d:0015::1a2f:1a3b', + ] + data = {'ttl': 30, 'values': values} record = AaaaRecord(self.zone, 'aaaa', data) self.assertEqual(a_values, record.values) @@ -341,15 +375,14 @@ class TestRecord(TestCase): a.__repr__() def test_caa(self): - a_values = [{ - 'flags': 0, - 'tag': 'issue', - 'value': 'ca.example.net', - }, { - 'flags': 128, - 'tag': 'iodef', - 'value': 'mailto:security@example.com', - }] + a_values = [ + {'flags': 0, 'tag': 'issue', 'value': 'ca.example.net'}, + { + 'flags': 128, + 'tag': 'iodef', + 'value': 'mailto:security@example.com', + }, + ] a_data = {'ttl': 30, 'values': a_values} a = CaaRecord(self.zone, 'a', a_data) self.assertEqual('a', a.name) @@ -363,10 +396,7 @@ class TestRecord(TestCase): self.assertEqual(a_values[1]['value'], a.values[1].value) self.assertEqual(a_data, a.data) - b_value = { - 'tag': 'iodef', - 'value': 'http://iodef.example.com/', - } + b_value = {'tag': 'iodef', 'value': 'http://iodef.example.com/'} b_data = {'ttl': 30, 'value': b_value} b = CaaRecord(self.zone, 'b', b_data) self.assertEqual(0, b.values[0].flags) @@ -401,28 +431,28 @@ class TestRecord(TestCase): a.__repr__() def test_cname(self): - self.assertSingleValue(CnameRecord, 'target.foo.com.', - 'other.foo.com.') + self.assertSingleValue(CnameRecord, 'target.foo.com.', 'other.foo.com.') def test_dname(self): - self.assertSingleValue(DnameRecord, 'target.foo.com.', - 'other.foo.com.') + self.assertSingleValue(DnameRecord, 'target.foo.com.', 'other.foo.com.') def test_loc(self): - a_values = [{ - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - }] + a_values = [ + { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + } + ] a_data = {'ttl': 30, 'values': a_values} a = LocRecord(self.zone, 'a', a_data) self.assertEqual('a', a.name) @@ -431,22 +461,23 @@ class TestRecord(TestCase): self.assertEqual(a_values[0]['lat_degrees'], a.values[0].lat_degrees) self.assertEqual(a_values[0]['lat_minutes'], a.values[0].lat_minutes) self.assertEqual(a_values[0]['lat_seconds'], a.values[0].lat_seconds) - self.assertEqual(a_values[0]['lat_direction'], - a.values[0].lat_direction) - self.assertEqual(a_values[0]['long_degrees'], - a.values[0].long_degrees) - self.assertEqual(a_values[0]['long_minutes'], - a.values[0].long_minutes) - self.assertEqual(a_values[0]['long_seconds'], - a.values[0].long_seconds) - self.assertEqual(a_values[0]['long_direction'], - a.values[0].long_direction) + self.assertEqual( + a_values[0]['lat_direction'], a.values[0].lat_direction + ) + self.assertEqual(a_values[0]['long_degrees'], a.values[0].long_degrees) + self.assertEqual(a_values[0]['long_minutes'], a.values[0].long_minutes) + self.assertEqual(a_values[0]['long_seconds'], a.values[0].long_seconds) + self.assertEqual( + a_values[0]['long_direction'], a.values[0].long_direction + ) self.assertEqual(a_values[0]['altitude'], a.values[0].altitude) self.assertEqual(a_values[0]['size'], a.values[0].size) - self.assertEqual(a_values[0]['precision_horz'], - a.values[0].precision_horz) - self.assertEqual(a_values[0]['precision_vert'], - a.values[0].precision_vert) + self.assertEqual( + a_values[0]['precision_horz'], a.values[0].precision_horz + ) + self.assertEqual( + a_values[0]['precision_vert'], a.values[0].precision_vert + ) b_value = { 'lat_degrees': 32, @@ -471,14 +502,11 @@ class TestRecord(TestCase): self.assertEqual(b_value['long_degrees'], b.values[0].long_degrees) self.assertEqual(b_value['long_minutes'], b.values[0].long_minutes) self.assertEqual(b_value['long_seconds'], b.values[0].long_seconds) - self.assertEqual(b_value['long_direction'], - b.values[0].long_direction) + self.assertEqual(b_value['long_direction'], b.values[0].long_direction) self.assertEqual(b_value['altitude'], b.values[0].altitude) self.assertEqual(b_value['size'], b.values[0].size) - self.assertEqual(b_value['precision_horz'], - b.values[0].precision_horz) - self.assertEqual(b_value['precision_vert'], - b.values[0].precision_vert) + self.assertEqual(b_value['precision_horz'], b.values[0].precision_horz) + self.assertEqual(b_value['precision_vert'], b.values[0].precision_vert) self.assertEqual(b_data, b.data) target = SimpleProvider() @@ -501,13 +529,10 @@ class TestRecord(TestCase): a.__repr__() def test_mx(self): - a_values = [{ - 'preference': 10, - 'exchange': 'smtp1.' - }, { - 'priority': 20, - 'value': 'smtp2.' - }] + a_values = [ + {'preference': 10, 'exchange': 'smtp1.'}, + {'priority': 20, 'value': 'smtp2.'}, + ] a_data = {'ttl': 30, 'values': a_values} a = MxRecord(self.zone, 'a', a_data) self.assertEqual('a', a.name) @@ -517,29 +542,20 @@ class TestRecord(TestCase): self.assertEqual(a_values[0]['exchange'], a.values[0].exchange) self.assertEqual(a_values[1]['priority'], a.values[1].preference) self.assertEqual(a_values[1]['value'], a.values[1].exchange) - a_data['values'][1] = { - 'preference': 20, - 'exchange': 'smtp2.', - } + a_data['values'][1] = {'preference': 20, 'exchange': 'smtp2.'} self.assertEqual(a_data, a.data) - b_value = { - 'preference': 0, - 'exchange': 'smtp3.', - } + b_value = {'preference': 0, 'exchange': 'smtp3.'} b_data = {'ttl': 30, 'value': b_value} b = MxRecord(self.zone, 'b', b_data) self.assertEqual(b_value['preference'], b.values[0].preference) self.assertEqual(b_value['exchange'], b.values[0].exchange) self.assertEqual(b_data, b.data) - a_upper_values = [{ - 'preference': 10, - 'exchange': 'SMTP1.' - }, { - 'priority': 20, - 'value': 'SMTP2.' - }] + a_upper_values = [ + {'preference': 10, 'exchange': 'SMTP1.'}, + {'priority': 20, 'value': 'SMTP2.'}, + ] a_upper_data = {'ttl': 30, 'values': a_upper_values} a_upper = MxRecord(self.zone, 'a', a_upper_data) self.assertEqual(a_upper.data, a.data) @@ -564,21 +580,24 @@ class TestRecord(TestCase): a.__repr__() def test_naptr(self): - a_values = [{ - 'order': 10, - 'preference': 11, - 'flags': 'X', - 'service': 'Y', - 'regexp': 'Z', - 'replacement': '.', - }, { - 'order': 20, - 'preference': 21, - 'flags': 'A', - 'service': 'B', - 'regexp': 'C', - 'replacement': 'foo.com', - }] + a_values = [ + { + 'order': 10, + 'preference': 11, + 'flags': 'X', + 'service': 'Y', + 'regexp': 'Z', + 'replacement': '.', + }, + { + 'order': 20, + 'preference': 21, + 'flags': 'A', + 'service': 'B', + 'regexp': 'C', + 'replacement': 'foo.com', + }, + ] a_data = {'ttl': 30, 'values': a_values} a = NaptrRecord(self.zone, 'a', a_data) self.assertEqual('a', a.name) @@ -627,128 +646,192 @@ class TestRecord(TestCase): self.assertTrue(b_naptr_value <= b_naptr_value) self.assertTrue(b_naptr_value >= b_naptr_value) # by order - self.assertTrue(b_naptr_value > NaptrValue({ - 'order': 10, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'x', - })) - self.assertTrue(b_naptr_value < NaptrValue({ - 'order': 40, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'x', - })) + self.assertTrue( + b_naptr_value + > NaptrValue( + { + 'order': 10, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) + self.assertTrue( + b_naptr_value + < NaptrValue( + { + 'order': 40, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) # by preference - self.assertTrue(b_naptr_value > NaptrValue({ - 'order': 30, - 'preference': 10, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'x', - })) - self.assertTrue(b_naptr_value < NaptrValue({ - 'order': 30, - 'preference': 40, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'x', - })) + self.assertTrue( + b_naptr_value + > NaptrValue( + { + 'order': 30, + 'preference': 10, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) + self.assertTrue( + b_naptr_value + < NaptrValue( + { + 'order': 30, + 'preference': 40, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) # by flags - self.assertTrue(b_naptr_value > NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'A', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'x', - })) - self.assertTrue(b_naptr_value < NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'Z', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'x', - })) + self.assertTrue( + b_naptr_value + > NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'A', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) + self.assertTrue( + b_naptr_value + < NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'Z', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) # by service - self.assertTrue(b_naptr_value > NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'A', - 'regexp': 'O', - 'replacement': 'x', - })) - self.assertTrue(b_naptr_value < NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'Z', - 'regexp': 'O', - 'replacement': 'x', - })) + self.assertTrue( + b_naptr_value + > NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'A', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) + self.assertTrue( + b_naptr_value + < NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'Z', + 'regexp': 'O', + 'replacement': 'x', + } + ) + ) # by regexp - self.assertTrue(b_naptr_value > NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'A', - 'replacement': 'x', - })) - self.assertTrue(b_naptr_value < NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'Z', - 'replacement': 'x', - })) + self.assertTrue( + b_naptr_value + > NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'A', + 'replacement': 'x', + } + ) + ) + self.assertTrue( + b_naptr_value + < NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'Z', + 'replacement': 'x', + } + ) + ) # by replacement - self.assertTrue(b_naptr_value > NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'a', - })) - self.assertTrue(b_naptr_value < NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'z', - })) + self.assertTrue( + b_naptr_value + > NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'a', + } + ) + ) + self.assertTrue( + b_naptr_value + < NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'z', + } + ) + ) # __repr__ doesn't blow up a.__repr__() # Hash - v = NaptrValue({ - 'order': 30, - 'preference': 31, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'z', - }) - o = NaptrValue({ - 'order': 30, - 'preference': 32, - 'flags': 'M', - 'service': 'N', - 'regexp': 'O', - 'replacement': 'z', - }) + v = NaptrValue( + { + 'order': 30, + 'preference': 31, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'z', + } + ) + o = NaptrValue( + { + 'order': 30, + 'preference': 32, + 'flags': 'M', + 'service': 'N', + 'regexp': 'O', + 'replacement': 'z', + } + ) values = set() values.add(v) self.assertTrue(v in values) @@ -773,23 +856,19 @@ class TestRecord(TestCase): self.assertEqual(b_data, b.data) def test_sshfp(self): - a_values = [{ - 'algorithm': 10, - 'fingerprint_type': 11, - 'fingerprint': 'abc123', - }, { - 'algorithm': 20, - 'fingerprint_type': 21, - 'fingerprint': 'def456', - }] + a_values = [ + {'algorithm': 10, 'fingerprint_type': 11, 'fingerprint': 'abc123'}, + {'algorithm': 20, 'fingerprint_type': 21, 'fingerprint': 'def456'}, + ] a_data = {'ttl': 30, 'values': a_values} a = SshfpRecord(self.zone, 'a', a_data) self.assertEqual('a', a.name) self.assertEqual('a.unit.tests.', a.fqdn) self.assertEqual(30, a.ttl) self.assertEqual(a_values[0]['algorithm'], a.values[0].algorithm) - self.assertEqual(a_values[0]['fingerprint_type'], - a.values[0].fingerprint_type) + self.assertEqual( + a_values[0]['fingerprint_type'], a.values[0].fingerprint_type + ) self.assertEqual(a_values[0]['fingerprint'], a.values[0].fingerprint) self.assertEqual(a_data, a.data) @@ -801,8 +880,9 @@ class TestRecord(TestCase): b_data = {'ttl': 30, 'value': b_value} b = SshfpRecord(self.zone, 'b', b_data) self.assertEqual(b_value['algorithm'], b.values[0].algorithm) - self.assertEqual(b_value['fingerprint_type'], - b.values[0].fingerprint_type) + self.assertEqual( + b_value['fingerprint_type'], b.values[0].fingerprint_type + ) self.assertEqual(b_value['fingerprint'], b.values[0].fingerprint) self.assertEqual(b_data, b.data) @@ -839,17 +919,10 @@ class TestRecord(TestCase): self.assertMultipleValues(SpfRecord, a_values, b_value) def test_srv(self): - a_values = [{ - 'priority': 10, - 'weight': 11, - 'port': 12, - 'target': 'server1', - }, { - 'priority': 20, - 'weight': 21, - 'port': 22, - 'target': 'server2', - }] + a_values = [ + {'priority': 10, 'weight': 11, 'port': 12, 'target': 'server1'}, + {'priority': 20, 'weight': 21, 'port': 22, 'target': 'server2'}, + ] a_data = {'ttl': 30, 'values': a_values} a = SrvRecord(self.zone, '_a._tcp', a_data) self.assertEqual('_a._tcp', a.name) @@ -879,8 +952,9 @@ class TestRecord(TestCase): # No changes with self self.assertFalse(a.changes(a, target)) # Diff in priority causes change - other = SrvRecord(self.zone, '_a._icmp', - {'ttl': 30, 'values': a_values}) + other = SrvRecord( + self.zone, '_a._icmp', {'ttl': 30, 'values': a_values} + ) other.values[0].priority = 22 change = a.changes(other, target) self.assertEqual(change.existing, a) @@ -908,38 +982,48 @@ class TestRecord(TestCase): a.__repr__() def test_tlsa(self): - a_values = [{ - 'certificate_usage': 1, - 'selector': 1, - 'matching_type': 1, - 'certificate_association_data': 'ABABABABABABABABAB', - }, { - 'certificate_usage': 2, - 'selector': 0, - 'matching_type': 2, - 'certificate_association_data': 'ABABABABABABABABAC', - }] + a_values = [ + { + 'certificate_usage': 1, + 'selector': 1, + 'matching_type': 1, + 'certificate_association_data': 'ABABABABABABABABAB', + }, + { + 'certificate_usage': 2, + 'selector': 0, + 'matching_type': 2, + 'certificate_association_data': 'ABABABABABABABABAC', + }, + ] a_data = {'ttl': 30, 'values': a_values} a = TlsaRecord(self.zone, 'a', a_data) self.assertEqual('a.unit.tests.', a.fqdn) self.assertEqual('a', a.name) self.assertEqual(30, a.ttl) - self.assertEqual(a_values[0]['certificate_usage'], - a.values[0].certificate_usage) + self.assertEqual( + a_values[0]['certificate_usage'], a.values[0].certificate_usage + ) self.assertEqual(a_values[0]['selector'], a.values[0].selector) - self.assertEqual(a_values[0]['matching_type'], - a.values[0].matching_type) - self.assertEqual(a_values[0]['certificate_association_data'], - a.values[0].certificate_association_data) + self.assertEqual( + a_values[0]['matching_type'], a.values[0].matching_type + ) + self.assertEqual( + a_values[0]['certificate_association_data'], + a.values[0].certificate_association_data, + ) - self.assertEqual(a_values[1]['certificate_usage'], - a.values[1].certificate_usage) - self.assertEqual(a_values[1]['selector'], - a.values[1].selector) - self.assertEqual(a_values[1]['matching_type'], - a.values[1].matching_type) - self.assertEqual(a_values[1]['certificate_association_data'], - a.values[1].certificate_association_data) + self.assertEqual( + a_values[1]['certificate_usage'], a.values[1].certificate_usage + ) + self.assertEqual(a_values[1]['selector'], a.values[1].selector) + self.assertEqual( + a_values[1]['matching_type'], a.values[1].matching_type + ) + self.assertEqual( + a_values[1]['certificate_association_data'], + a.values[1].certificate_association_data, + ) self.assertEqual(a_data, a.data) b_value = { @@ -950,13 +1034,15 @@ class TestRecord(TestCase): } b_data = {'ttl': 30, 'value': b_value} b = TlsaRecord(self.zone, 'b', b_data) - self.assertEqual(b_value['certificate_usage'], - b.values[0].certificate_usage) + self.assertEqual( + b_value['certificate_usage'], b.values[0].certificate_usage + ) self.assertEqual(b_value['selector'], b.values[0].selector) - self.assertEqual(b_value['matching_type'], - b.values[0].matching_type) - self.assertEqual(b_value['certificate_association_data'], - b.values[0].certificate_association_data) + self.assertEqual(b_value['matching_type'], b.values[0].matching_type) + self.assertEqual( + b_value['certificate_association_data'], + b.values[0].certificate_association_data, + ) self.assertEqual(b_data, b.data) target = SimpleProvider() @@ -996,19 +1082,22 @@ class TestRecord(TestCase): self.assertMultipleValues(TxtRecord, a_values, b_value) def test_urlfwd(self): - a_values = [{ - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 2, - 'query': 0, - }, { - 'path': '/target', - 'target': 'http://target', - 'code': 302, - 'masking': 2, - 'query': 0, - }] + a_values = [ + { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + 'query': 0, + }, + { + 'path': '/target', + 'target': 'http://target', + 'code': 302, + 'masking': 2, + 'query': 0, + }, + ] a_data = {'ttl': 30, 'values': a_values} a = UrlfwdRecord(self.zone, 'a', a_data) self.assertEqual('a', a.name) @@ -1077,20 +1166,24 @@ class TestRecord(TestCase): self.assertEqual(change.new, other) # hash - v = UrlfwdValue({ - 'path': '/', - 'target': 'http://place', - 'code': 301, - 'masking': 2, - 'query': 0, - }) - o = UrlfwdValue({ - 'path': '/location', - 'target': 'http://redirect', - 'code': 302, - 'masking': 2, - 'query': 0, - }) + v = UrlfwdValue( + { + 'path': '/', + 'target': 'http://place', + 'code': 301, + 'masking': 2, + 'query': 0, + } + ) + o = UrlfwdValue( + { + 'path': '/location', + 'target': 'http://redirect', + 'code': 302, + 'masking': 2, + 'query': 0, + } + ) values = set() values.add(v) self.assertTrue(v in values) @@ -1102,11 +1195,9 @@ class TestRecord(TestCase): a.__repr__() def test_record_new(self): - txt = Record.new(self.zone, 'txt', { - 'ttl': 44, - 'type': 'TXT', - 'value': 'some text', - }) + txt = Record.new( + self.zone, 'txt', {'ttl': 44, 'type': 'TXT', 'value': 'some text'} + ) self.assertIsInstance(txt, TxtRecord) self.assertEqual('TXT', txt._type) self.assertEqual(['some text'], txt.values) @@ -1118,17 +1209,13 @@ class TestRecord(TestCase): # Unknown type with self.assertRaises(Exception) as ctx: - Record.new(self.zone, 'unknown', { - 'type': 'XXX', - }) + Record.new(self.zone, 'unknown', {'type': 'XXX'}) self.assertTrue('Unknown record type' in str(ctx.exception)) def test_record_copy(self): - a = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - }) + a = Record.new( + self.zone, 'a', {'ttl': 44, 'type': 'A', 'value': '1.2.3.4'} + ) # Identical copy. b = a.copy() @@ -1148,10 +1235,7 @@ class TestRecord(TestCase): self.assertEqual(['1.2.3.4'], c.values) # Record with no record type specified in data. - d_data = { - 'ttl': 600, - 'values': ['just a test'] - } + d_data = {'ttl': 600, 'values': ['just a test']} d = TxtRecord(self.zone, 'txt', d_data) d.copy() self.assertEqual('TXT', d._type) @@ -1159,45 +1243,25 @@ class TestRecord(TestCase): def test_dynamic_record_copy(self): a_data = { 'dynamic': { - 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }], - }, - }, - 'rules': [{ - 'pool': 'one', - }], - }, - 'octodns': { - 'healthcheck': { - 'protocol': 'TCP', - 'port': 80, - }, + 'pools': {'one': {'values': [{'value': '3.3.3.3'}]}}, + 'rules': [{'pool': 'one'}], }, + 'octodns': {'healthcheck': {'protocol': 'TCP', 'port': 80}}, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } record1 = Record.new(self.zone, 'a', a_data) record2 = record1.copy() self.assertEqual(record1._octodns, record2._octodns) def test_change(self): - existing = Record.new(self.zone, 'txt', { - 'ttl': 44, - 'type': 'TXT', - 'value': 'some text', - }) - new = Record.new(self.zone, 'txt', { - 'ttl': 44, - 'type': 'TXT', - 'value': 'some change', - }) + existing = Record.new( + self.zone, 'txt', {'ttl': 44, 'type': 'TXT', 'value': 'some text'} + ) + new = Record.new( + self.zone, 'txt', {'ttl': 44, 'type': 'TXT', 'value': 'some change'} + ) create = Create(new) self.assertEqual(new.values, create.record.values) update = Update(existing, new) @@ -1249,131 +1313,141 @@ class TestRecord(TestCase): self.assertTrue(c >= b) def test_healthcheck(self): - new = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - 'octodns': { - 'healthcheck': { - 'path': '/_ready', - 'host': 'bleep.bloop', - 'protocol': 'HTTP', - 'port': 8080, - } - } - }) + new = Record.new( + self.zone, + 'a', + { + 'ttl': 44, + 'type': 'A', + 'value': '1.2.3.4', + 'octodns': { + 'healthcheck': { + 'path': '/_ready', + 'host': 'bleep.bloop', + 'protocol': 'HTTP', + 'port': 8080, + } + }, + }, + ) self.assertEqual('/_ready', new.healthcheck_path) self.assertEqual('bleep.bloop', new.healthcheck_host()) self.assertEqual('HTTP', new.healthcheck_protocol) self.assertEqual(8080, new.healthcheck_port) # empty host value in healthcheck - new = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - 'octodns': { - 'healthcheck': { - 'path': '/_ready', - 'host': None, - 'protocol': 'HTTP', - 'port': 8080, - } - } - }) + new = Record.new( + self.zone, + 'a', + { + 'ttl': 44, + 'type': 'A', + 'value': '1.2.3.4', + 'octodns': { + 'healthcheck': { + 'path': '/_ready', + 'host': None, + 'protocol': 'HTTP', + 'port': 8080, + } + }, + }, + ) self.assertEqual('1.2.3.4', new.healthcheck_host(value="1.2.3.4")) - new = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - }) + new = Record.new( + self.zone, 'a', {'ttl': 44, 'type': 'A', 'value': '1.2.3.4'} + ) self.assertEqual('/_dns', new.healthcheck_path) self.assertEqual('a.unit.tests', new.healthcheck_host()) self.assertEqual('HTTPS', new.healthcheck_protocol) self.assertEqual(443, new.healthcheck_port) def test_healthcheck_tcp(self): - new = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - 'octodns': { - 'healthcheck': { - 'path': '/ignored', - 'host': 'completely.ignored', - 'protocol': 'TCP', - 'port': 8080, - } - } - }) + new = Record.new( + self.zone, + 'a', + { + 'ttl': 44, + 'type': 'A', + 'value': '1.2.3.4', + 'octodns': { + 'healthcheck': { + 'path': '/ignored', + 'host': 'completely.ignored', + 'protocol': 'TCP', + 'port': 8080, + } + }, + }, + ) self.assertIsNone(new.healthcheck_path) self.assertIsNone(new.healthcheck_host()) self.assertEqual('TCP', new.healthcheck_protocol) self.assertEqual(8080, new.healthcheck_port) - new = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - 'octodns': { - 'healthcheck': { - 'protocol': 'TCP', - } - } - }) + new = Record.new( + self.zone, + 'a', + { + 'ttl': 44, + 'type': 'A', + 'value': '1.2.3.4', + 'octodns': {'healthcheck': {'protocol': 'TCP'}}, + }, + ) self.assertIsNone(new.healthcheck_path) self.assertIsNone(new.healthcheck_host()) self.assertEqual('TCP', new.healthcheck_protocol) self.assertEqual(443, new.healthcheck_port) def test_inored(self): - new = Record.new(self.zone, 'txt', { - 'ttl': 44, - 'type': 'TXT', - 'value': 'some change', - 'octodns': { - 'ignored': True, - } - }) + new = Record.new( + self.zone, + 'txt', + { + 'ttl': 44, + 'type': 'TXT', + 'value': 'some change', + 'octodns': {'ignored': True}, + }, + ) self.assertTrue(new.ignored) - new = Record.new(self.zone, 'txt', { - 'ttl': 44, - 'type': 'TXT', - 'value': 'some change', - 'octodns': { - 'ignored': False, - } - }) + new = Record.new( + self.zone, + 'txt', + { + 'ttl': 44, + 'type': 'TXT', + 'value': 'some change', + 'octodns': {'ignored': False}, + }, + ) self.assertFalse(new.ignored) - new = Record.new(self.zone, 'txt', { - 'ttl': 44, - 'type': 'TXT', - 'value': 'some change', - }) + new = Record.new( + self.zone, 'txt', {'ttl': 44, 'type': 'TXT', 'value': 'some change'} + ) self.assertFalse(new.ignored) def test_ordering_functions(self): - a = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - }) - b = Record.new(self.zone, 'b', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - }) - c = Record.new(self.zone, 'c', { - 'ttl': 44, - 'type': 'A', - 'value': '1.2.3.4', - }) - aaaa = Record.new(self.zone, 'a', { - 'ttl': 44, - 'type': 'AAAA', - 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', - }) + a = Record.new( + self.zone, 'a', {'ttl': 44, 'type': 'A', 'value': '1.2.3.4'} + ) + b = Record.new( + self.zone, 'b', {'ttl': 44, 'type': 'A', 'value': '1.2.3.4'} + ) + c = Record.new( + self.zone, 'c', {'ttl': 44, 'type': 'A', 'value': '1.2.3.4'} + ) + aaaa = Record.new( + self.zone, + 'a', + { + 'ttl': 44, + 'type': 'AAAA', + 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', + }, + ) self.assertEqual(a, a) self.assertEqual(b, b) @@ -1485,48 +1559,54 @@ class TestRecord(TestCase): self.assertTrue(d <= d) def test_loc_value(self): - a = LocValue({ - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - }) - b = LocValue({ - 'lat_degrees': 32, - 'lat_minutes': 7, - 'lat_seconds': 19, - 'lat_direction': 'S', - 'long_degrees': 116, - 'long_minutes': 2, - 'long_seconds': 25, - 'long_direction': 'E', - 'altitude': 10, - 'size': 1, - 'precision_horz': 10000, - 'precision_vert': 10, - }) - c = LocValue({ - 'lat_degrees': 53, - 'lat_minutes': 14, - 'lat_seconds': 10, - 'lat_direction': 'N', - 'long_degrees': 2, - 'long_minutes': 18, - 'long_seconds': 26, - 'long_direction': 'W', - 'altitude': 10, - 'size': 1, - 'precision_horz': 1000, - 'precision_vert': 10, - }) + a = LocValue( + { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + } + ) + b = LocValue( + { + 'lat_degrees': 32, + 'lat_minutes': 7, + 'lat_seconds': 19, + 'lat_direction': 'S', + 'long_degrees': 116, + 'long_minutes': 2, + 'long_seconds': 25, + 'long_direction': 'E', + 'altitude': 10, + 'size': 1, + 'precision_horz': 10000, + 'precision_vert': 10, + } + ) + c = LocValue( + { + 'lat_degrees': 53, + 'lat_minutes': 14, + 'lat_seconds': 10, + 'lat_direction': 'N', + 'long_degrees': 2, + 'long_minutes': 18, + 'long_seconds': 26, + 'long_direction': 'W', + 'altitude': 10, + 'size': 1, + 'precision_horz': 1000, + 'precision_vert': 10, + } + ) self.assertEqual(a, a) self.assertEqual(b, b) @@ -1572,12 +1652,15 @@ class TestRecord(TestCase): self.assertTrue(b in values) def test_mx_value(self): - a = MxValue({'preference': 0, 'priority': 'a', 'exchange': 'v', - 'value': '1'}) - b = MxValue({'preference': 10, 'priority': 'a', 'exchange': 'v', - 'value': '2'}) - c = MxValue({'preference': 0, 'priority': 'b', 'exchange': 'z', - 'value': '3'}) + a = MxValue( + {'preference': 0, 'priority': 'a', 'exchange': 'v', 'value': '1'} + ) + b = MxValue( + {'preference': 10, 'priority': 'a', 'exchange': 'v', 'value': '2'} + ) + c = MxValue( + {'preference': 0, 'priority': 'b', 'exchange': 'z', 'value': '3'} + ) self.assertEqual(a, a) self.assertEqual(b, b) @@ -1618,14 +1701,18 @@ class TestRecord(TestCase): self.assertNotEqual(a.__hash__(), b.__hash__()) def test_sshfp_value(self): - a = SshfpValue({'algorithm': 0, 'fingerprint_type': 0, - 'fingerprint': 'abcd'}) - b = SshfpValue({'algorithm': 1, 'fingerprint_type': 0, - 'fingerprint': 'abcd'}) - c = SshfpValue({'algorithm': 0, 'fingerprint_type': 1, - 'fingerprint': 'abcd'}) - d = SshfpValue({'algorithm': 0, 'fingerprint_type': 0, - 'fingerprint': 'bcde'}) + a = SshfpValue( + {'algorithm': 0, 'fingerprint_type': 0, 'fingerprint': 'abcd'} + ) + b = SshfpValue( + {'algorithm': 1, 'fingerprint_type': 0, 'fingerprint': 'abcd'} + ) + c = SshfpValue( + {'algorithm': 0, 'fingerprint_type': 1, 'fingerprint': 'abcd'} + ) + d = SshfpValue( + {'algorithm': 0, 'fingerprint_type': 0, 'fingerprint': 'bcde'} + ) self.assertEqual(a, a) self.assertEqual(b, b) @@ -1751,11 +1838,9 @@ class TestRecordValidation(TestCase): # name = '@' with self.assertRaises(ValidationError) as ctx: name = '@' - Record.new(self.zone, name, { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4', - }) + Record.new( + self.zone, name, {'ttl': 300, 'type': 'A', 'value': '1.2.3.4'} + ) reason = ctx.exception.reasons[0] self.assertTrue(reason.startswith('invalid name "@", use "" instead')) @@ -1763,718 +1848,704 @@ class TestRecordValidation(TestCase): with self.assertRaises(ValidationError) as ctx: # The . will put this over the edge name = 'x' * (253 - len(self.zone.name)) - Record.new(self.zone, name, { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4', - }) + Record.new( + self.zone, name, {'ttl': 300, 'type': 'A', 'value': '1.2.3.4'} + ) reason = ctx.exception.reasons[0] self.assertTrue(reason.startswith('invalid fqdn, "xxxx')) - self.assertTrue(reason.endswith('.unit.tests." is too long at 254' - ' chars, max is 253')) + self.assertTrue( + reason.endswith( + '.unit.tests." is too long at 254' ' chars, max is 253' + ) + ) # label length, DNS defines max as 63 with self.assertRaises(ValidationError) as ctx: # The . will put this over the edge name = 'x' * 64 - Record.new(self.zone, name, { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4', - }) + Record.new( + self.zone, name, {'ttl': 300, 'type': 'A', 'value': '1.2.3.4'} + ) 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')) + self.assertTrue( + reason.endswith('xxx" is too long at 64' ' chars, max is 63') + ) with self.assertRaises(ValidationError) as ctx: name = 'foo.' + 'x' * 64 + '.bar' - Record.new(self.zone, name, { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4', - }) + Record.new( + self.zone, name, {'ttl': 300, 'type': 'A', 'value': '1.2.3.4'} + ) 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')) + self.assertTrue( + reason.endswith('xxx" is too long at 64' ' chars, max is 63') + ) # should not raise with dots name = 'xxxxxxxx.' * 10 - Record.new(self.zone, name, { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4', - }) + Record.new( + self.zone, name, {'ttl': 300, 'type': 'A', 'value': '1.2.3.4'} + ) # no ttl with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'A', - 'value': '1.2.3.4', - }) + Record.new(self.zone, '', {'type': 'A', 'value': '1.2.3.4'}) self.assertEqual(['missing ttl'], ctx.exception.reasons) # invalid ttl with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': -1, - 'value': '1.2.3.4', - }) + Record.new( + self.zone, 'www', {'type': 'A', 'ttl': -1, 'value': '1.2.3.4'} + ) self.assertEqual('www.unit.tests.', ctx.exception.fqdn) self.assertEqual(['invalid ttl'], ctx.exception.reasons) # no exception if we're in lenient mode - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': -1, - 'value': '1.2.3.4', - }, lenient=True) + Record.new( + self.zone, + 'www', + {'type': 'A', 'ttl': -1, 'value': '1.2.3.4'}, + lenient=True, + ) # __init__ may still blow up, even if validation is lenient with self.assertRaises(KeyError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': -1, - }, lenient=True) + Record.new(self.zone, 'www', {'type': 'A', 'ttl': -1}, lenient=True) self.assertEqual(('value',), ctx.exception.args) # no exception if we're in lenient mode from config - Record.new(self.zone, 'www', { - 'octodns': { - 'lenient': True + Record.new( + self.zone, + 'www', + { + 'octodns': {'lenient': True}, + 'type': 'A', + 'ttl': -1, + 'value': '1.2.3.4', }, - 'type': 'A', - 'ttl': -1, - 'value': '1.2.3.4', - }, lenient=True) + lenient=True, + ) def test_A_and_values_mixin(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - }) - Record.new(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': [ - '1.2.3.4', - ] - }) - Record.new(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': [ - '1.2.3.4', - '1.2.3.5', - ] - }) + Record.new(self.zone, '', {'type': 'A', 'ttl': 600, 'value': '1.2.3.4'}) + Record.new( + self.zone, '', {'type': 'A', 'ttl': 600, 'values': ['1.2.3.4']} + ) + Record.new( + self.zone, + '', + {'type': 'A', 'ttl': 600, 'values': ['1.2.3.4', '1.2.3.5']}, + ) # missing value(s), no value or value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'A', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'A', 'ttl': 600}) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing value(s), empty values with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': 600, - 'values': [] - }) + Record.new( + self.zone, 'www', {'type': 'A', 'ttl': 600, 'values': []} + ) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing value(s), None values with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': 600, - 'values': None - }) + Record.new( + self.zone, 'www', {'type': 'A', 'ttl': 600, 'values': None} + ) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing value(s) and empty value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': 600, - 'values': [None, ''] - }) - self.assertEqual(['missing value(s)', - 'empty value'], ctx.exception.reasons) + Record.new( + self.zone, + 'www', + {'type': 'A', 'ttl': 600, 'values': [None, '']}, + ) + self.assertEqual( + ['missing value(s)', 'empty value'], ctx.exception.reasons + ) # missing value(s), None value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': 600, - 'value': None - }) + Record.new( + self.zone, 'www', {'type': 'A', 'ttl': 600, 'value': None} + ) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # empty value, empty string value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'A', - 'ttl': 600, - 'value': '' - }) + Record.new(self.zone, 'www', {'type': 'A', 'ttl': 600, 'value': ''}) self.assertEqual(['empty value'], ctx.exception.reasons) # missing value(s) & ttl with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'A', - }) - self.assertEqual(['missing ttl', 'missing value(s)'], - ctx.exception.reasons) + Record.new(self.zone, '', {'type': 'A'}) + self.assertEqual( + ['missing ttl', 'missing value(s)'], ctx.exception.reasons + ) # invalid ipv4 address with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'value': 'hello' - }) - self.assertEqual(['invalid IPv4 address "hello"'], - ctx.exception.reasons) + Record.new( + self.zone, '', {'type': 'A', 'ttl': 600, 'value': 'hello'} + ) + self.assertEqual( + ['invalid IPv4 address "hello"'], ctx.exception.reasons + ) # invalid ipv4 addresses with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'A', - 'ttl': 600, - 'values': ['hello', 'goodbye'] - }) - self.assertEqual([ - 'invalid IPv4 address "hello"', - 'invalid IPv4 address "goodbye"' - ], ctx.exception.reasons) + Record.new( + self.zone, + '', + {'type': 'A', 'ttl': 600, 'values': ['hello', 'goodbye']}, + ) + self.assertEqual( + ['invalid IPv4 address "hello"', 'invalid IPv4 address "goodbye"'], + ctx.exception.reasons, + ) # invalid & valid ipv4 addresses, no ttl with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'A', - 'values': ['1.2.3.4', 'hello', '5.6.7.8'] - }) - self.assertEqual([ - 'missing ttl', - 'invalid IPv4 address "hello"', - ], ctx.exception.reasons) + Record.new( + self.zone, + '', + {'type': 'A', 'values': ['1.2.3.4', 'hello', '5.6.7.8']}, + ) + self.assertEqual( + ['missing ttl', 'invalid IPv4 address "hello"'], + ctx.exception.reasons, + ) def test_AAAA_validation(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', - }) - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'values': [ - '2601:644:500:e210:62f8:1dff:feb8:947a', - ] - }) - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'values': [ - '2601:644:500:e210:62f8:1dff:feb8:947a', - '2601:642:500:e210:62f8:1dff:feb8:947a', - ] - }) + Record.new( + self.zone, + '', + { + 'type': 'AAAA', + 'ttl': 600, + 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', + }, + ) + Record.new( + self.zone, + '', + { + 'type': 'AAAA', + 'ttl': 600, + 'values': ['2601:644:500:e210:62f8:1dff:feb8:947a'], + }, + ) + Record.new( + self.zone, + '', + { + 'type': 'AAAA', + 'ttl': 600, + 'values': [ + '2601:644:500:e210:62f8:1dff:feb8:947a', + '2601:642:500:e210:62f8:1dff:feb8:947a', + ], + }, + ) # missing value(s), no value or value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'AAAA', 'ttl': 600}) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing value(s), empty values with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'AAAA', - 'ttl': 600, - 'values': [] - }) + Record.new( + self.zone, 'www', {'type': 'AAAA', 'ttl': 600, 'values': []} + ) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing value(s), None values with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'AAAA', - 'ttl': 600, - 'values': None - }) + Record.new( + self.zone, 'www', {'type': 'AAAA', 'ttl': 600, 'values': None} + ) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing value(s) and empty value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'AAAA', - 'ttl': 600, - 'values': [None, ''] - }) - self.assertEqual(['missing value(s)', - 'empty value'], ctx.exception.reasons) + Record.new( + self.zone, + 'www', + {'type': 'AAAA', 'ttl': 600, 'values': [None, '']}, + ) + self.assertEqual( + ['missing value(s)', 'empty value'], ctx.exception.reasons + ) # missing value(s), None value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'AAAA', - 'ttl': 600, - 'value': None - }) + Record.new( + self.zone, 'www', {'type': 'AAAA', 'ttl': 600, 'value': None} + ) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # empty value, empty string value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'AAAA', - 'ttl': 600, - 'value': '' - }) + Record.new( + self.zone, 'www', {'type': 'AAAA', 'ttl': 600, 'value': ''} + ) self.assertEqual(['empty value'], ctx.exception.reasons) # missing value(s) & ttl with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'AAAA', - }) - self.assertEqual(['missing ttl', 'missing value(s)'], - ctx.exception.reasons) + Record.new(self.zone, '', {'type': 'AAAA'}) + self.assertEqual( + ['missing ttl', 'missing value(s)'], ctx.exception.reasons + ) # invalid IPv6 address with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'value': 'hello' - }) - self.assertEqual(['invalid IPv6 address "hello"'], - ctx.exception.reasons) + Record.new( + self.zone, '', {'type': 'AAAA', 'ttl': 600, 'value': 'hello'} + ) + self.assertEqual( + ['invalid IPv6 address "hello"'], ctx.exception.reasons + ) # invalid IPv6 addresses with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'values': ['hello', 'goodbye'] - }) - self.assertEqual([ - 'invalid IPv6 address "hello"', - 'invalid IPv6 address "goodbye"' - ], ctx.exception.reasons) + Record.new( + self.zone, + '', + {'type': 'AAAA', 'ttl': 600, 'values': ['hello', 'goodbye']}, + ) + self.assertEqual( + ['invalid IPv6 address "hello"', 'invalid IPv6 address "goodbye"'], + ctx.exception.reasons, + ) # invalid & valid IPv6 addresses, no ttl with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'AAAA', - 'values': [ - '2601:644:500:e210:62f8:1dff:feb8:947a', - 'hello', - '2601:642:500:e210:62f8:1dff:feb8:947a' - ] - }) - self.assertEqual([ - 'missing ttl', - 'invalid IPv6 address "hello"', - ], ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'AAAA', + 'values': [ + '2601:644:500:e210:62f8:1dff:feb8:947a', + 'hello', + '2601:642:500:e210:62f8:1dff:feb8:947a', + ], + }, + ) + self.assertEqual( + ['missing ttl', 'invalid IPv6 address "hello"'], + ctx.exception.reasons, + ) def test_geo(self): - Record.new(self.zone, '', { - 'geo': { - 'NA': ['1.2.3.5'], - 'NA-US': ['1.2.3.5', '1.2.3.6'] + Record.new( + self.zone, + '', + { + 'geo': {'NA': ['1.2.3.5'], 'NA-US': ['1.2.3.5', '1.2.3.6']}, + 'type': 'A', + 'ttl': 600, + 'value': '1.2.3.4', }, - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - }) + ) # invalid ip address with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'geo': { - 'NA': ['hello'], - 'NA-US': ['1.2.3.5', '1.2.3.6'] + Record.new( + self.zone, + '', + { + 'geo': {'NA': ['hello'], 'NA-US': ['1.2.3.5', '1.2.3.6']}, + 'type': 'A', + 'ttl': 600, + 'value': '1.2.3.4', }, - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - }) - self.assertEqual(['invalid IPv4 address "hello"'], - ctx.exception.reasons) + ) + self.assertEqual( + ['invalid IPv4 address "hello"'], ctx.exception.reasons + ) # invalid geo code with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'geo': { - 'XYZ': ['1.2.3.4'], + Record.new( + self.zone, + '', + { + 'geo': {'XYZ': ['1.2.3.4']}, + 'type': 'A', + 'ttl': 600, + 'value': '1.2.3.4', }, - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - }) + ) self.assertEqual(['invalid geo "XYZ"'], ctx.exception.reasons) # invalid ip address with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'geo': { - 'NA': ['hello'], - 'NA-US': ['1.2.3.5', 'goodbye'] + Record.new( + self.zone, + '', + { + 'geo': {'NA': ['hello'], 'NA-US': ['1.2.3.5', 'goodbye']}, + 'type': 'A', + 'ttl': 600, + 'value': '1.2.3.4', }, - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - }) - self.assertEqual([ - 'invalid IPv4 address "hello"', - 'invalid IPv4 address "goodbye"' - ], ctx.exception.reasons) + ) + self.assertEqual( + ['invalid IPv4 address "hello"', 'invalid IPv4 address "goodbye"'], + ctx.exception.reasons, + ) # invalid healthcheck protocol with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'a', { - 'geo': { - 'NA': ['1.2.3.5'], - 'NA-US': ['1.2.3.5', '1.2.3.6'] + Record.new( + self.zone, + 'a', + { + 'geo': {'NA': ['1.2.3.5'], 'NA-US': ['1.2.3.5', '1.2.3.6']}, + 'type': 'A', + 'ttl': 600, + 'value': '1.2.3.4', + 'octodns': {'healthcheck': {'protocol': 'FTP'}}, }, - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - 'octodns': { - 'healthcheck': { - 'protocol': 'FTP', - } - } - }) - self.assertEqual(['invalid healthcheck protocol'], - ctx.exception.reasons) + ) + self.assertEqual( + ['invalid healthcheck protocol'], ctx.exception.reasons + ) def test_AAAA(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', - }) - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'values': [ - '2601:644:500:e210:62f8:1dff:feb8:947a', - '2601:644:500:e210:62f8:1dff:feb8:947b', - ] - }) - - # invalid ip address - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { + Record.new( + self.zone, + '', + { 'type': 'AAAA', 'ttl': 600, - 'value': 'hello' - }) - self.assertEqual(['invalid IPv6 address "hello"'], - ctx.exception.reasons) - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { + 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', + }, + ) + Record.new( + self.zone, + '', + { 'type': 'AAAA', 'ttl': 600, 'values': [ - '1.2.3.4', - '2.3.4.5', + '2601:644:500:e210:62f8:1dff:feb8:947a', + '2601:644:500:e210:62f8:1dff:feb8:947b', ], - }) - self.assertEqual([ - 'invalid IPv6 address "1.2.3.4"', - 'invalid IPv6 address "2.3.4.5"', - ], ctx.exception.reasons) + }, + ) + + # invalid ip address + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, '', {'type': 'AAAA', 'ttl': 600, 'value': 'hello'} + ) + self.assertEqual( + ['invalid IPv6 address "hello"'], ctx.exception.reasons + ) + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + '', + {'type': 'AAAA', 'ttl': 600, 'values': ['1.2.3.4', '2.3.4.5']}, + ) + self.assertEqual( + [ + 'invalid IPv6 address "1.2.3.4"', + 'invalid IPv6 address "2.3.4.5"', + ], + ctx.exception.reasons, + ) # invalid ip addresses with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'AAAA', - 'ttl': 600, - 'values': ['hello', 'goodbye'] - }) - self.assertEqual([ - 'invalid IPv6 address "hello"', - 'invalid IPv6 address "goodbye"' - ], ctx.exception.reasons) + Record.new( + self.zone, + '', + {'type': 'AAAA', 'ttl': 600, 'values': ['hello', 'goodbye']}, + ) + self.assertEqual( + ['invalid IPv6 address "hello"', 'invalid IPv6 address "goodbye"'], + ctx.exception.reasons, + ) def test_ALIAS_and_value_mixin(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) + Record.new( + self.zone, + '', + {'type': 'ALIAS', 'ttl': 600, 'value': 'foo.bar.com.'}, + ) # root only with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'nope', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) - self.assertEqual(['non-root ALIAS not allowed'], - ctx.exception.reasons) + Record.new( + self.zone, + 'nope', + {'type': 'ALIAS', 'ttl': 600, 'value': 'foo.bar.com.'}, + ) + self.assertEqual(['non-root ALIAS not allowed'], ctx.exception.reasons) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'ALIAS', 'ttl': 600}) self.assertEqual(['missing value'], ctx.exception.reasons) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': None - }) + Record.new( + self.zone, '', {'type': 'ALIAS', 'ttl': 600, 'value': None} + ) self.assertEqual(['missing value'], ctx.exception.reasons) # empty value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': '' - }) + Record.new( + self.zone, '', {'type': 'ALIAS', 'ttl': 600, 'value': ''} + ) self.assertEqual(['empty value'], ctx.exception.reasons) # not a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': '__.', - }) - self.assertEqual(['ALIAS value "__." is not a valid FQDN'], - ctx.exception.reasons) + Record.new( + self.zone, '', {'type': 'ALIAS', 'ttl': 600, 'value': '__.'} + ) + self.assertEqual( + ['ALIAS value "__." is not a valid FQDN'], ctx.exception.reasons + ) # missing trailing . with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'ALIAS', - 'ttl': 600, - 'value': 'foo.bar.com', - }) - self.assertEqual(['ALIAS value "foo.bar.com" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + {'type': 'ALIAS', 'ttl': 600, 'value': 'foo.bar.com'}, + ) + self.assertEqual( + ['ALIAS value "foo.bar.com" missing trailing .'], + ctx.exception.reasons, + ) def test_CAA(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'CAA', - 'ttl': 600, - 'value': { - 'flags': 128, - 'tag': 'iodef', - 'value': 'http://foo.bar.com/' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'CAA', + 'ttl': 600, + 'value': { + 'flags': 128, + 'tag': 'iodef', + 'value': 'http://foo.bar.com/', + }, + }, + ) # invalid flags with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'CAA', - 'ttl': 600, - 'value': { - 'flags': -42, - 'tag': 'iodef', - 'value': 'http://foo.bar.com/', - } - }) + Record.new( + self.zone, + '', + { + 'type': 'CAA', + 'ttl': 600, + 'value': { + 'flags': -42, + 'tag': 'iodef', + 'value': 'http://foo.bar.com/', + }, + }, + ) self.assertEqual(['invalid flags "-42"'], ctx.exception.reasons) with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'CAA', - 'ttl': 600, - 'value': { - 'flags': 442, - 'tag': 'iodef', - 'value': 'http://foo.bar.com/', - } - }) + Record.new( + self.zone, + '', + { + 'type': 'CAA', + 'ttl': 600, + 'value': { + 'flags': 442, + 'tag': 'iodef', + 'value': 'http://foo.bar.com/', + }, + }, + ) self.assertEqual(['invalid flags "442"'], ctx.exception.reasons) with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'CAA', - 'ttl': 600, - 'value': { - 'flags': 'nope', - 'tag': 'iodef', - 'value': 'http://foo.bar.com/', - } - }) + Record.new( + self.zone, + '', + { + 'type': 'CAA', + 'ttl': 600, + 'value': { + 'flags': 'nope', + 'tag': 'iodef', + 'value': 'http://foo.bar.com/', + }, + }, + ) self.assertEqual(['invalid flags "nope"'], ctx.exception.reasons) # missing tag with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'CAA', - 'ttl': 600, - 'value': { - 'value': 'http://foo.bar.com/', - } - }) + Record.new( + self.zone, + '', + { + 'type': 'CAA', + 'ttl': 600, + 'value': {'value': 'http://foo.bar.com/'}, + }, + ) self.assertEqual(['missing tag'], ctx.exception.reasons) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'CAA', - 'ttl': 600, - 'value': { - 'tag': 'iodef', - } - }) + Record.new( + self.zone, + '', + {'type': 'CAA', 'ttl': 600, 'value': {'tag': 'iodef'}}, + ) self.assertEqual(['missing value'], ctx.exception.reasons) def test_CNAME(self): # doesn't blow up - Record.new(self.zone, 'www', { - 'type': 'CNAME', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) + Record.new( + self.zone, + 'www', + {'type': 'CNAME', 'ttl': 600, 'value': 'foo.bar.com.'}, + ) # root cname is a no-no with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'CNAME', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) + Record.new( + self.zone, + '', + {'type': 'CNAME', 'ttl': 600, 'value': 'foo.bar.com.'}, + ) self.assertEqual(['root CNAME not allowed'], ctx.exception.reasons) # not a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'CNAME', - 'ttl': 600, - 'value': '___.', - }) - self.assertEqual(['CNAME value "___." is not a valid FQDN'], - ctx.exception.reasons) + Record.new( + self.zone, 'www', {'type': 'CNAME', 'ttl': 600, 'value': '___.'} + ) + self.assertEqual( + ['CNAME value "___." is not a valid FQDN'], ctx.exception.reasons + ) # missing trailing . with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'CNAME', - 'ttl': 600, - 'value': 'foo.bar.com', - }) - self.assertEqual(['CNAME value "foo.bar.com" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, + 'www', + {'type': 'CNAME', 'ttl': 600, 'value': 'foo.bar.com'}, + ) + self.assertEqual( + ['CNAME value "foo.bar.com" missing trailing .'], + ctx.exception.reasons, + ) # doesn't allow urls with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'CNAME', - 'ttl': 600, - 'value': 'https://google.com', - }) - self.assertEqual(['CNAME value "https://google.com" is not a valid ' - 'FQDN'], ctx.exception.reasons) + Record.new( + self.zone, + 'www', + {'type': 'CNAME', 'ttl': 600, 'value': 'https://google.com'}, + ) + self.assertEqual( + ['CNAME value "https://google.com" is not a valid ' 'FQDN'], + ctx.exception.reasons, + ) # doesn't allow urls with paths with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'CNAME', - 'ttl': 600, - 'value': 'https://google.com/a/b/c', - }) - self.assertEqual(['CNAME value "https://google.com/a/b/c" is not a ' - 'valid FQDN'], ctx.exception.reasons) + Record.new( + self.zone, + 'www', + { + 'type': 'CNAME', + 'ttl': 600, + 'value': 'https://google.com/a/b/c', + }, + ) + self.assertEqual( + ['CNAME value "https://google.com/a/b/c" is not a ' 'valid FQDN'], + ctx.exception.reasons, + ) # doesn't allow paths with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'CNAME', - 'ttl': 600, - 'value': 'google.com/some/path', - }) - self.assertEqual(['CNAME value "google.com/some/path" is not a valid ' - 'FQDN'], ctx.exception.reasons) + Record.new( + self.zone, + 'www', + {'type': 'CNAME', 'ttl': 600, 'value': 'google.com/some/path'}, + ) + self.assertEqual( + ['CNAME value "google.com/some/path" is not a valid ' 'FQDN'], + ctx.exception.reasons, + ) def test_DNAME(self): # A valid DNAME record. - Record.new(self.zone, 'sub', { - 'type': 'DNAME', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) + Record.new( + self.zone, + 'sub', + {'type': 'DNAME', 'ttl': 600, 'value': 'foo.bar.com.'}, + ) # A DNAME record can be present at the zone APEX. - Record.new(self.zone, '', { - 'type': 'DNAME', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) + Record.new( + self.zone, + '', + {'type': 'DNAME', 'ttl': 600, 'value': 'foo.bar.com.'}, + ) # not a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'DNAME', - 'ttl': 600, - 'value': '.', - }) - self.assertEqual(['DNAME value "." is not a valid FQDN'], - ctx.exception.reasons) + Record.new( + self.zone, 'www', {'type': 'DNAME', 'ttl': 600, 'value': '.'} + ) + self.assertEqual( + ['DNAME value "." is not a valid FQDN'], ctx.exception.reasons + ) # missing trailing . with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'www', { - 'type': 'DNAME', - 'ttl': 600, - 'value': 'foo.bar.com', - }) - self.assertEqual(['DNAME value "foo.bar.com" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, + 'www', + {'type': 'DNAME', 'ttl': 600, 'value': 'foo.bar.com'}, + ) + self.assertEqual( + ['DNAME value "foo.bar.com" missing trailing .'], + ctx.exception.reasons, + ) def test_LOC(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) - - # missing int key - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { + Record.new( + self.zone, + '', + { 'type': 'LOC', 'ttl': 600, 'value': { + 'lat_degrees': 31, 'lat_minutes': 58, 'lat_seconds': 52.1, 'lat_direction': 'S', @@ -2486,364 +2557,450 @@ class TestRecordValidation(TestCase): 'size': 10, 'precision_horz': 10, 'precision_vert': 2, - } - }) + }, + }, + ) + + # missing int key + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) self.assertEqual(['missing lat_degrees'], ctx.exception.reasons) # missing float key with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) self.assertEqual(['missing lat_seconds'], ctx.exception.reasons) # missing text key with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) self.assertEqual(['missing lat_direction'], ctx.exception.reasons) # invalid direction with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'U', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'U', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid direction for lat_direction "U"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid direction for lat_direction "U"'], ctx.exception.reasons + ) with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'N', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'N', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid direction for long_direction "N"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid direction for long_direction "N"'], ctx.exception.reasons + ) # invalid degrees with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 360, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 360, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid value for lat_degrees "360"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid value for lat_degrees "360"'], ctx.exception.reasons + ) with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 'nope', - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 'nope', + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid lat_degrees "nope"'], - ctx.exception.reasons) + self.assertEqual(['invalid lat_degrees "nope"'], ctx.exception.reasons) # invalid minutes with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 60, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 60, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid value for lat_minutes "60"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid value for lat_minutes "60"'], ctx.exception.reasons + ) # invalid seconds with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 60, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 60, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid value for lat_seconds "60"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid value for lat_seconds "60"'], ctx.exception.reasons + ) with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 'nope', - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 'nope', + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid lat_seconds "nope"'], - ctx.exception.reasons) + self.assertEqual(['invalid lat_seconds "nope"'], ctx.exception.reasons) # invalid altitude with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': -666666, - 'size': 10, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': -666666, + 'size': 10, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid value for altitude "-666666"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid value for altitude "-666666"'], ctx.exception.reasons + ) # invalid size with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'LOC', - 'ttl': 600, - 'value': { - 'lat_degrees': 31, - 'lat_minutes': 58, - 'lat_seconds': 52.1, - 'lat_direction': 'S', - 'long_degrees': 115, - 'long_minutes': 49, - 'long_seconds': 11.7, - 'long_direction': 'E', - 'altitude': 20, - 'size': 99999999.99, - 'precision_horz': 10, - 'precision_vert': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'LOC', + 'ttl': 600, + 'value': { + 'lat_degrees': 31, + 'lat_minutes': 58, + 'lat_seconds': 52.1, + 'lat_direction': 'S', + 'long_degrees': 115, + 'long_minutes': 49, + 'long_seconds': 11.7, + 'long_direction': 'E', + 'altitude': 20, + 'size': 99999999.99, + 'precision_horz': 10, + 'precision_vert': 2, + }, + }, + ) - self.assertEqual(['invalid value for size "99999999.99"'], - ctx.exception.reasons) + self.assertEqual( + ['invalid value for size "99999999.99"'], ctx.exception.reasons + ) def test_MX(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'preference': 10, - 'exchange': 'foo.bar.com.' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'MX', + 'ttl': 600, + 'value': {'preference': 10, 'exchange': 'foo.bar.com.'}, + }, + ) # missing preference with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'exchange': 'foo.bar.com.' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'MX', + 'ttl': 600, + 'value': {'exchange': 'foo.bar.com.'}, + }, + ) self.assertEqual(['missing preference'], ctx.exception.reasons) # invalid preference with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'preference': 'nope', - 'exchange': 'foo.bar.com.' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'MX', + 'ttl': 600, + 'value': {'preference': 'nope', 'exchange': 'foo.bar.com.'}, + }, + ) self.assertEqual(['invalid preference "nope"'], ctx.exception.reasons) # missing exchange with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'preference': 10, - } - }) + Record.new( + self.zone, + '', + {'type': 'MX', 'ttl': 600, 'value': {'preference': 10}}, + ) self.assertEqual(['missing exchange'], ctx.exception.reasons) # missing trailing . with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'preference': 10, - 'exchange': 'foo.bar.com' - } - }) - self.assertEqual(['MX value "foo.bar.com" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'MX', + 'ttl': 600, + 'value': {'preference': 10, 'exchange': 'foo.bar.com'}, + }, + ) + self.assertEqual( + ['MX value "foo.bar.com" missing trailing .'], ctx.exception.reasons + ) # exchange must be a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'preference': 10, - 'exchange': '100 foo.bar.com.' - } - }) - self.assertEqual(['Invalid MX exchange "100 foo.bar.com." is not a ' - 'valid FQDN.'], ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'MX', + 'ttl': 600, + 'value': {'preference': 10, 'exchange': '100 foo.bar.com.'}, + }, + ) + self.assertEqual( + ['Invalid MX exchange "100 foo.bar.com." is not a ' 'valid FQDN.'], + ctx.exception.reasons, + ) # exchange can be a single `.` - record = Record.new(self.zone, '', { - 'type': 'MX', - 'ttl': 600, - 'value': { - 'preference': 0, - 'exchange': '.' - } - }) + record = Record.new( + self.zone, + '', + { + 'type': 'MX', + 'ttl': 600, + 'value': {'preference': 0, 'exchange': '.'}, + }, + ) self.assertEqual('.', record.values[0].exchange) def test_NXPTR(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'NAPTR', - 'ttl': 600, - 'value': { - 'order': 10, - 'preference': 20, - 'flags': 'S', - 'service': 'srv', - 'regexp': '.*', - 'replacement': '.' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'NAPTR', + 'ttl': 600, + 'value': { + 'order': 10, + 'preference': 20, + 'flags': 'S', + 'service': 'srv', + 'regexp': '.*', + 'replacement': '.', + }, + }, + ) # missing X priority value = { @@ -2852,692 +3009,830 @@ class TestRecordValidation(TestCase): 'flags': 'S', 'service': 'srv', 'regexp': '.*', - 'replacement': '.' + 'replacement': '.', } - for k in ('order', 'preference', 'flags', 'service', 'regexp', - 'replacement'): + for k in ( + 'order', + 'preference', + 'flags', + 'service', + 'regexp', + 'replacement', + ): v = dict(value) del v[k] with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NAPTR', - 'ttl': 600, - 'value': v - }) + Record.new( + self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v} + ) self.assertEqual([f'missing {k}'], ctx.exception.reasons) # non-int order v = dict(value) v['order'] = 'boo' with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NAPTR', - 'ttl': 600, - 'value': v - }) + Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v}) self.assertEqual(['invalid order "boo"'], ctx.exception.reasons) # non-int preference v = dict(value) v['preference'] = 'who' with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NAPTR', - 'ttl': 600, - 'value': v - }) + Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v}) self.assertEqual(['invalid preference "who"'], ctx.exception.reasons) # unrecognized flags v = dict(value) v['flags'] = 'X' with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NAPTR', - 'ttl': 600, - 'value': v - }) + Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v}) self.assertEqual(['unrecognized flags "X"'], ctx.exception.reasons) def test_NS(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'NS', - 'ttl': 600, - 'values': [ - 'foo.bar.com.', - '1.2.3.4.' - ] - }) + Record.new( + self.zone, + '', + {'type': 'NS', 'ttl': 600, 'values': ['foo.bar.com.', '1.2.3.4.']}, + ) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NS', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'NS', 'ttl': 600}) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # no trailing . with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NS', - 'ttl': 600, - 'value': 'foo.bar', - }) - self.assertEqual(['NS value "foo.bar" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, '', {'type': 'NS', 'ttl': 600, 'value': 'foo.bar'} + ) + self.assertEqual( + ['NS value "foo.bar" missing trailing .'], ctx.exception.reasons + ) # exchange must be a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'NS', - 'ttl': 600, - 'value': '100 foo.bar.com.' - }) - self.assertEqual(['Invalid NS value "100 foo.bar.com." is not a ' - 'valid FQDN.'], ctx.exception.reasons) + Record.new( + self.zone, + '', + {'type': 'NS', 'ttl': 600, 'value': '100 foo.bar.com.'}, + ) + self.assertEqual( + ['Invalid NS value "100 foo.bar.com." is not a ' 'valid FQDN.'], + ctx.exception.reasons, + ) def test_PTR(self): # doesn't blow up (name & zone here don't make any sense, but not # important) - Record.new(self.zone, '', { - 'type': 'PTR', - 'ttl': 600, - 'value': 'foo.bar.com.', - }) + Record.new( + self.zone, '', {'type': 'PTR', 'ttl': 600, 'value': 'foo.bar.com.'} + ) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'PTR', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'PTR', 'ttl': 600}) self.assertEqual(['missing values'], ctx.exception.reasons) # not a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'PTR', - 'ttl': 600, - 'value': '_.', - }) - self.assertEqual(['PTR value "_." is not a valid FQDN'], - ctx.exception.reasons) + Record.new( + self.zone, '', {'type': 'PTR', 'ttl': 600, 'value': '_.'} + ) + self.assertEqual( + ['PTR value "_." is not a valid FQDN'], ctx.exception.reasons + ) # no trailing . with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'PTR', - 'ttl': 600, - 'value': 'foo.bar', - }) - self.assertEqual(['PTR value "foo.bar" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, '', {'type': 'PTR', 'ttl': 600, 'value': 'foo.bar'} + ) + self.assertEqual( + ['PTR value "foo.bar" missing trailing .'], ctx.exception.reasons + ) def test_SSHFP(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 1, - 'fingerprint_type': 1, - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) - - # missing algorithm - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { + Record.new( + self.zone, + '', + { 'type': 'SSHFP', 'ttl': 600, 'value': { + 'algorithm': 1, 'fingerprint_type': 1, - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) + + # missing algorithm + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': { + 'fingerprint_type': 1, + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) self.assertEqual(['missing algorithm'], ctx.exception.reasons) # invalid algorithm with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 'nope', - 'fingerprint_type': 2, - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': { + 'algorithm': 'nope', + 'fingerprint_type': 2, + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) self.assertEqual(['invalid algorithm "nope"'], ctx.exception.reasons) # unrecognized algorithm with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 42, - 'fingerprint_type': 1, - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) - self.assertEqual(['unrecognized algorithm "42"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': { + 'algorithm': 42, + 'fingerprint_type': 1, + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) + self.assertEqual(['unrecognized algorithm "42"'], ctx.exception.reasons) # missing fingerprint_type with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 2, - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': { + 'algorithm': 2, + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) self.assertEqual(['missing fingerprint_type'], ctx.exception.reasons) # invalid fingerprint_type with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 3, - 'fingerprint_type': 'yeeah', - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) - self.assertEqual(['invalid fingerprint_type "yeeah"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': { + 'algorithm': 3, + 'fingerprint_type': 'yeeah', + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) + self.assertEqual( + ['invalid fingerprint_type "yeeah"'], ctx.exception.reasons + ) # unrecognized fingerprint_type with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 1, - 'fingerprint_type': 42, - 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73' - } - }) - self.assertEqual(['unrecognized fingerprint_type "42"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': { + 'algorithm': 1, + 'fingerprint_type': 42, + 'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73', + }, + }, + ) + self.assertEqual( + ['unrecognized fingerprint_type "42"'], ctx.exception.reasons + ) # missing fingerprint with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SSHFP', - 'ttl': 600, - 'value': { - 'algorithm': 1, - 'fingerprint_type': 1, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'SSHFP', + 'ttl': 600, + 'value': {'algorithm': 1, 'fingerprint_type': 1}, + }, + ) self.assertEqual(['missing fingerprint'], ctx.exception.reasons) def test_SPF(self): # doesn't blow up (name & zone here don't make any sense, but not # important) - Record.new(self.zone, '', { - 'type': 'SPF', - 'ttl': 600, - 'values': [ - 'v=spf1 ip4:192.168.0.1/16-all', - 'v=spf1 ip4:10.1.2.1/24-all', - 'this has some\\; semi-colons\\; in it', - ] - }) + Record.new( + self.zone, + '', + { + 'type': 'SPF', + 'ttl': 600, + 'values': [ + 'v=spf1 ip4:192.168.0.1/16-all', + 'v=spf1 ip4:10.1.2.1/24-all', + 'this has some\\; semi-colons\\; in it', + ], + }, + ) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SPF', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'SPF', 'ttl': 600}) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing escapes with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'SPF', - 'ttl': 600, - 'value': 'this has some; semi-colons\\; in it', - }) - self.assertEqual(['unescaped ; in "this has some; ' - 'semi-colons\\; in it"'], ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'SPF', + 'ttl': 600, + 'value': 'this has some; semi-colons\\; in it', + }, + ) + self.assertEqual( + ['unescaped ; in "this has some; ' 'semi-colons\\; in it"'], + ctx.exception.reasons, + ) def test_SRV(self): # doesn't blow up - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'port': 3, - 'target': 'foo.bar.baz.' - } - }) - - # permit wildcard entries - Record.new(self.zone, '*._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'port': 3, - 'target': 'food.bar.baz.' - } - }) - - # invalid name - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, 'neup', { + Record.new( + self.zone, + '_srv._tcp', + { 'type': 'SRV', 'ttl': 600, 'value': { 'priority': 1, 'weight': 2, 'port': 3, - 'target': 'foo.bar.baz.' - } - }) - self.assertEqual(['invalid name for SRV record'], - ctx.exception.reasons) + 'target': 'foo.bar.baz.', + }, + }, + ) - # missing priority - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { + # permit wildcard entries + Record.new( + self.zone, + '*._tcp', + { 'type': 'SRV', 'ttl': 600, 'value': { + 'priority': 1, 'weight': 2, 'port': 3, - 'target': 'foo.bar.baz.' - } - }) + 'target': 'food.bar.baz.', + }, + }, + ) + + # invalid name + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + 'neup', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'weight': 2, + 'port': 3, + 'target': 'foo.bar.baz.', + }, + }, + ) + self.assertEqual(['invalid name for SRV record'], ctx.exception.reasons) + + # missing priority + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': {'weight': 2, 'port': 3, 'target': 'foo.bar.baz.'}, + }, + ) self.assertEqual(['missing priority'], ctx.exception.reasons) # invalid priority with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 'foo', - 'weight': 2, - 'port': 3, - 'target': 'foo.bar.baz.' - } - }) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 'foo', + 'weight': 2, + 'port': 3, + 'target': 'foo.bar.baz.', + }, + }, + ) self.assertEqual(['invalid priority "foo"'], ctx.exception.reasons) # missing weight with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'port': 3, - 'target': 'foo.bar.baz.' - } - }) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'port': 3, + 'target': 'foo.bar.baz.', + }, + }, + ) self.assertEqual(['missing weight'], ctx.exception.reasons) # invalid weight with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 'foo', - 'port': 3, - 'target': 'foo.bar.baz.' - } - }) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'weight': 'foo', + 'port': 3, + 'target': 'foo.bar.baz.', + }, + }, + ) self.assertEqual(['invalid weight "foo"'], ctx.exception.reasons) # missing port with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'target': 'foo.bar.baz.' - } - }) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'weight': 2, + 'target': 'foo.bar.baz.', + }, + }, + ) self.assertEqual(['missing port'], ctx.exception.reasons) # invalid port with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'port': 'foo', - 'target': 'foo.bar.baz.' - } - }) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'weight': 2, + 'port': 'foo', + 'target': 'foo.bar.baz.', + }, + }, + ) self.assertEqual(['invalid port "foo"'], ctx.exception.reasons) # missing target with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'port': 3, - } - }) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': {'priority': 1, 'weight': 2, 'port': 3}, + }, + ) self.assertEqual(['missing target'], ctx.exception.reasons) # invalid target with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'port': 3, - 'target': 'foo.bar.baz' - } - }) - self.assertEqual(['SRV value "foo.bar.baz" missing trailing .'], - ctx.exception.reasons) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'weight': 2, + 'port': 3, + 'target': 'foo.bar.baz', + }, + }, + ) + self.assertEqual( + ['SRV value "foo.bar.baz" missing trailing .'], + ctx.exception.reasons, + ) # target must be a valid FQDN with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '_srv._tcp', { - 'type': 'SRV', - 'ttl': 600, - 'value': { - 'priority': 1, - 'weight': 2, - 'port': 3, - 'target': '100 foo.bar.com.' - } - }) - self.assertEqual(['Invalid SRV target "100 foo.bar.com." is not a ' - 'valid FQDN.'], ctx.exception.reasons) + Record.new( + self.zone, + '_srv._tcp', + { + 'type': 'SRV', + 'ttl': 600, + 'value': { + 'priority': 1, + 'weight': 2, + 'port': 3, + 'target': '100 foo.bar.com.', + }, + }, + ) + self.assertEqual( + ['Invalid SRV target "100 foo.bar.com." is not a ' 'valid FQDN.'], + ctx.exception.reasons, + ) def test_TLSA(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 0, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - # Multi value, second missing certificate usage - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { + Record.new( + self.zone, + '', + { 'type': 'TLSA', 'ttl': 600, - 'values': [{ + 'value': { 'certificate_usage': 0, 'selector': 0, 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - }, { - 'selector': 0, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - ] - }) - self.assertEqual(['missing certificate_usage'], - ctx.exception.reasons) + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + # Multi value, second missing certificate usage + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'values': [ + { + 'certificate_usage': 0, + 'selector': 0, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + { + 'selector': 0, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + ], + }, + ) + self.assertEqual( + ['missing certificate_usage'], ctx.exception.reasons + ) # missing certificate_association_data with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 0, - 'matching_type': 0 - } - }) - self.assertEqual(['missing certificate_association_data'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'selector': 0, + 'matching_type': 0, + }, + }, + ) + self.assertEqual( + ['missing certificate_association_data'], ctx.exception.reasons + ) # missing certificate_usage with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'selector': 0, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual(['missing certificate_usage'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'selector': 0, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + ['missing certificate_usage'], ctx.exception.reasons + ) # False certificate_usage with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 4, - 'selector': 0, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual('invalid certificate_usage ' - '"{value["certificate_usage"]}"', - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 4, + 'selector': 0, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + 'invalid certificate_usage ' '"{value["certificate_usage"]}"', + ctx.exception.reasons, + ) # Invalid certificate_usage with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 'XYZ', - 'selector': 0, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual('invalid certificate_usage ' - '"{value["certificate_usage"]}"', - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 'XYZ', + 'selector': 0, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + 'invalid certificate_usage ' '"{value["certificate_usage"]}"', + ctx.exception.reasons, + ) # missing selector with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual(['missing selector'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual(['missing selector'], ctx.exception.reasons) # False selector with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 4, - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual('invalid selector ' - '"{value["selector"]}"', - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'selector': 4, + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + 'invalid selector ' '"{value["selector"]}"', + ctx.exception.reasons, + ) # Invalid selector with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 'XYZ', - 'matching_type': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual('invalid selector ' - '"{value["selector"]}"', - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'selector': 'XYZ', + 'matching_type': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + 'invalid selector ' '"{value["selector"]}"', + ctx.exception.reasons, + ) # missing matching_type with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 0, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual(['missing matching_type'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'selector': 0, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual(['missing matching_type'], ctx.exception.reasons) # False matching_type with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 1, - 'matching_type': 3, - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual('invalid matching_type ' - '"{value["matching_type"]}"', - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'selector': 1, + 'matching_type': 3, + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + 'invalid matching_type ' '"{value["matching_type"]}"', + ctx.exception.reasons, + ) # Invalid matching_type with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TLSA', - 'ttl': 600, - 'value': { - 'certificate_usage': 0, - 'selector': 1, - 'matching_type': 'XYZ', - 'certificate_association_data': 'AAAAAAAAAAAAA' - } - }) - self.assertEqual('invalid matching_type ' - '"{value["matching_type"]}"', - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TLSA', + 'ttl': 600, + 'value': { + 'certificate_usage': 0, + 'selector': 1, + 'matching_type': 'XYZ', + 'certificate_association_data': 'AAAAAAAAAAAAA', + }, + }, + ) + self.assertEqual( + 'invalid matching_type ' '"{value["matching_type"]}"', + ctx.exception.reasons, + ) def test_TXT(self): # doesn't blow up (name & zone here don't make any sense, but not # important) - Record.new(self.zone, '', { - 'type': 'TXT', - 'ttl': 600, - 'values': [ - 'hello world', - 'this has some\\; semi-colons\\; in it', - ] - }) + Record.new( + self.zone, + '', + { + 'type': 'TXT', + 'ttl': 600, + 'values': [ + 'hello world', + 'this has some\\; semi-colons\\; in it', + ], + }, + ) # missing value with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TXT', - 'ttl': 600, - }) + Record.new(self.zone, '', {'type': 'TXT', 'ttl': 600}) self.assertEqual(['missing value(s)'], ctx.exception.reasons) # missing escapes with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'TXT', - 'ttl': 600, - 'value': 'this has some; semi-colons\\; in it', - }) - self.assertEqual(['unescaped ; in "this has some; semi-colons\\; ' - 'in it"'], ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'TXT', + 'ttl': 600, + 'value': 'this has some; semi-colons\\; in it', + }, + ) + self.assertEqual( + ['unescaped ; in "this has some; semi-colons\\; ' 'in it"'], + ctx.exception.reasons, + ) def test_TXT_long_value_chunking(self): - expected = '"Lorem ipsum dolor sit amet, consectetur adipiscing ' \ - 'elit, sed do eiusmod tempor incididunt ut labore et dolore ' \ - 'magna aliqua. Ut enim ad minim veniam, quis nostrud ' \ - 'exercitation ullamco laboris nisi ut aliquip ex ea commodo ' \ - 'consequat. Duis aute irure dolor i" "n reprehenderit in ' \ - 'voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' \ - 'Excepteur sint occaecat cupidatat non proident, sunt in culpa ' \ + expected = ( + '"Lorem ipsum dolor sit amet, consectetur adipiscing ' + 'elit, sed do eiusmod tempor incididunt ut labore et dolore ' + 'magna aliqua. Ut enim ad minim veniam, quis nostrud ' + 'exercitation ullamco laboris nisi ut aliquip ex ea commodo ' + 'consequat. Duis aute irure dolor i" "n reprehenderit in ' + 'voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' + 'Excepteur sint occaecat cupidatat non proident, sunt in culpa ' 'qui officia deserunt mollit anim id est laborum."' + ) - long_value = 'Lorem ipsum dolor sit amet, consectetur adipiscing ' \ - 'elit, sed do eiusmod tempor incididunt ut labore et dolore ' \ - 'magna aliqua. Ut enim ad minim veniam, quis nostrud ' \ - 'exercitation ullamco laboris nisi ut aliquip ex ea commodo ' \ - 'consequat. Duis aute irure dolor in reprehenderit in ' \ - 'voluptate velit esse cillum dolore eu fugiat nulla ' \ - 'pariatur. Excepteur sint occaecat cupidatat non proident, ' \ - 'sunt in culpa qui officia deserunt mollit anim id est ' \ + long_value = ( + 'Lorem ipsum dolor sit amet, consectetur adipiscing ' + 'elit, sed do eiusmod tempor incididunt ut labore et dolore ' + 'magna aliqua. Ut enim ad minim veniam, quis nostrud ' + 'exercitation ullamco laboris nisi ut aliquip ex ea commodo ' + 'consequat. Duis aute irure dolor in reprehenderit in ' + 'voluptate velit esse cillum dolore eu fugiat nulla ' + 'pariatur. Excepteur sint occaecat cupidatat non proident, ' + 'sunt in culpa qui officia deserunt mollit anim id est ' 'laborum.' + ) # Single string - single = Record.new(self.zone, '', { - 'type': 'TXT', - 'ttl': 600, - 'values': [ - 'hello world', - long_value, - 'this has some\\; semi-colons\\; in it', - ] - }) + single = Record.new( + self.zone, + '', + { + 'type': 'TXT', + 'ttl': 600, + 'values': [ + 'hello world', + long_value, + 'this has some\\; semi-colons\\; in it', + ], + }, + ) self.assertEqual(3, len(single.values)) self.assertEqual(3, len(single.chunked_values)) # Note we are checking that this normalizes the chunking, not that we # get out what we put in. self.assertEqual(expected, single.chunked_values[0]) - long_split_value = '"Lorem ipsum dolor sit amet, consectetur ' \ - 'adipiscing elit, sed do eiusmod tempor incididunt ut ' \ - 'labore et dolore magna aliqua. Ut enim ad minim veniam, ' \ - 'quis nostrud exercitation ullamco laboris nisi ut aliquip ' \ - 'ex" " ea commodo consequat. Duis aute irure dolor in ' \ - 'reprehenderit in voluptate velit esse cillum dolore eu ' \ - 'fugiat nulla pariatur. Excepteur sint occaecat cupidatat ' \ - 'non proident, sunt in culpa qui officia deserunt mollit ' \ + long_split_value = ( + '"Lorem ipsum dolor sit amet, consectetur ' + 'adipiscing elit, sed do eiusmod tempor incididunt ut ' + 'labore et dolore magna aliqua. Ut enim ad minim veniam, ' + 'quis nostrud exercitation ullamco laboris nisi ut aliquip ' + 'ex" " ea commodo consequat. Duis aute irure dolor in ' + 'reprehenderit in voluptate velit esse cillum dolore eu ' + 'fugiat nulla pariatur. Excepteur sint occaecat cupidatat ' + 'non proident, sunt in culpa qui officia deserunt mollit ' 'anim id est laborum."' + ) # Chunked - chunked = Record.new(self.zone, '', { - 'type': 'TXT', - 'ttl': 600, - 'values': [ - '"hello world"', - long_split_value, - '"this has some\\; semi-colons\\; in it"', - ] - }) + chunked = Record.new( + self.zone, + '', + { + 'type': 'TXT', + 'ttl': 600, + 'values': [ + '"hello world"', + long_split_value, + '"this has some\\; semi-colons\\; in it"', + ], + }, + ) self.assertEqual(expected, chunked.chunked_values[0]) # should be single values, no quoting self.assertEqual(single.values, chunked.values) @@ -3546,200 +3841,259 @@ class TestRecordValidation(TestCase): def test_URLFWD(self): # doesn't blow up - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 2, - 'query': 0, - } - }) - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'values': [{ - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 2, - 'query': 0, - }, { - 'path': '/target', - 'target': 'http://target', - 'code': 302, - 'masking': 2, - 'query': 0, - }] - }) - - # missing path - with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { + Record.new( + self.zone, + '', + { 'type': 'URLFWD', 'ttl': 600, 'value': { + 'path': '/', 'target': 'http://foo', 'code': 301, 'masking': 2, 'query': 0, - } - }) + }, + }, + ) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'values': [ + { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + 'query': 0, + }, + { + 'path': '/target', + 'target': 'http://target', + 'code': 302, + 'masking': 2, + 'query': 0, + }, + ], + }, + ) + + # missing path + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + 'query': 0, + }, + }, + ) self.assertEqual(['missing path'], ctx.exception.reasons) # missing target with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'code': 301, - 'masking': 2, - 'query': 0, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'code': 301, + 'masking': 2, + 'query': 0, + }, + }, + ) self.assertEqual(['missing target'], ctx.exception.reasons) # missing code with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'masking': 2, - 'query': 0, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'masking': 2, + 'query': 0, + }, + }, + ) self.assertEqual(['missing code'], ctx.exception.reasons) # invalid code with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 'nope', - 'masking': 2, - 'query': 0, - } - }) - self.assertEqual(['invalid return code "nope"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 'nope', + 'masking': 2, + 'query': 0, + }, + }, + ) + self.assertEqual(['invalid return code "nope"'], ctx.exception.reasons) # unrecognized code with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 3, - 'masking': 2, - 'query': 0, - } - }) - self.assertEqual(['unrecognized return code "3"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 3, + 'masking': 2, + 'query': 0, + }, + }, + ) + self.assertEqual( + ['unrecognized return code "3"'], ctx.exception.reasons + ) # missing masking with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'query': 0, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'query': 0, + }, + }, + ) self.assertEqual(['missing masking'], ctx.exception.reasons) # invalid masking with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 'nope', - 'query': 0, - } - }) - self.assertEqual(['invalid masking setting "nope"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 'nope', + 'query': 0, + }, + }, + ) + self.assertEqual( + ['invalid masking setting "nope"'], ctx.exception.reasons + ) # unrecognized masking with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 3, - 'query': 0, - } - }) - self.assertEqual(['unrecognized masking setting "3"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 3, + 'query': 0, + }, + }, + ) + self.assertEqual( + ['unrecognized masking setting "3"'], ctx.exception.reasons + ) # missing query with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 2, - } - }) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + }, + }, + ) self.assertEqual(['missing query'], ctx.exception.reasons) # invalid query with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 2, - 'query': 'nope', - } - }) - self.assertEqual(['invalid query setting "nope"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + 'query': 'nope', + }, + }, + ) + self.assertEqual( + ['invalid query setting "nope"'], ctx.exception.reasons + ) # unrecognized query with self.assertRaises(ValidationError) as ctx: - Record.new(self.zone, '', { - 'type': 'URLFWD', - 'ttl': 600, - 'value': { - 'path': '/', - 'target': 'http://foo', - 'code': 301, - 'masking': 2, - 'query': 3, - } - }) - self.assertEqual(['unrecognized query setting "3"'], - ctx.exception.reasons) + Record.new( + self.zone, + '', + { + 'type': 'URLFWD', + 'ttl': 600, + 'value': { + 'path': '/', + 'target': 'http://foo', + 'code': 301, + 'masking': 2, + 'query': 3, + }, + }, + ) + self.assertEqual( + ['unrecognized query setting "3"'], ctx.exception.reasons + ) class TestDynamicRecords(TestCase): @@ -3749,45 +4103,26 @@ class TestDynamicRecords(TestCase): a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'weight': 10, - 'value': '3.3.3.3', - }], - }, + 'one': {'values': [{'weight': 10, 'value': '3.3.3.3'}]}, 'two': { # Testing out of order value sorting here - 'values': [{ - 'value': '5.5.5.5', - }, { - 'value': '4.4.4.4', - }], + 'values': [{'value': '5.5.5.5'}, {'value': '4.4.4.4'}] }, 'three': { - 'values': [{ - 'weight': 10, - 'value': '4.4.4.4', - }, { - 'weight': 12, - 'value': '5.5.5.5', - }], + 'values': [ + {'weight': 10, 'value': '4.4.4.4'}, + {'weight': 12, 'value': '5.5.5.5'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } a = ARecord(self.zone, 'weighted', a_data) self.assertEqual('A', a._type) @@ -3799,29 +4134,24 @@ class TestDynamicRecords(TestCase): pools = dynamic.pools self.assertTrue(pools) - self.assertEqual({ - 'value': '3.3.3.3', - 'weight': 1, - 'status': 'obey', - }, pools['one'].data['values'][0]) - self.assertEqual([{ - 'value': '4.4.4.4', - 'weight': 1, - 'status': 'obey', - }, { - 'value': '5.5.5.5', - 'weight': 1, - 'status': 'obey', - }], pools['two'].data['values']) - self.assertEqual([{ - 'weight': 10, - 'value': '4.4.4.4', - 'status': 'obey', - }, { - 'weight': 12, - 'value': '5.5.5.5', - 'status': 'obey', - }], pools['three'].data['values']) + self.assertEqual( + {'value': '3.3.3.3', 'weight': 1, 'status': 'obey'}, + pools['one'].data['values'][0], + ) + self.assertEqual( + [ + {'value': '4.4.4.4', 'weight': 1, 'status': 'obey'}, + {'value': '5.5.5.5', 'weight': 1, 'status': 'obey'}, + ], + pools['two'].data['values'], + ) + self.assertEqual( + [ + {'weight': 10, 'value': '4.4.4.4', 'status': 'obey'}, + {'weight': 12, 'value': '5.5.5.5', 'status': 'obey'}, + ], + pools['three'].data['values'], + ) rules = dynamic.rules self.assertTrue(rules) @@ -3847,13 +4177,7 @@ class TestDynamicRecords(TestCase): 2: '2601:642:500:e210:62f8:1dff:feb8:9477', }, }, - 'rules': [{ - 'pools': [ - 'three', - 'two', - 'one', - ], - }], + 'rules': [{'pools': ['three', 'two', 'one']}], }, 'ttl': 60, 'values': [ @@ -3865,37 +4189,35 @@ class TestDynamicRecords(TestCase): 'dynamic': { 'pools': { 'one': { - 'values': [{ - 'value': '2601:642:500:e210:62f8:1dff:feb8:9473', - }], + 'values': [ + {'value': '2601:642:500:e210:62f8:1dff:feb8:9473'} + ] }, 'two': { # Testing out of order value sorting here - 'values': [{ - 'value': '2601:642:500:e210:62f8:1dff:feb8:9475', - }, { - 'value': '2601:642:500:e210:62f8:1dff:feb8:9474', - }], + 'values': [ + {'value': '2601:642:500:e210:62f8:1dff:feb8:9475'}, + {'value': '2601:642:500:e210:62f8:1dff:feb8:9474'}, + ] }, 'three': { - 'values': [{ - 'weight': 10, - 'value': '2601:642:500:e210:62f8:1dff:feb8:9476', - }, { - 'weight': 12, - 'value': '2601:642:500:e210:62f8:1dff:feb8:9477', - }], + 'values': [ + { + 'weight': 10, + 'value': '2601:642:500:e210:62f8:1dff:feb8:9476', + }, + { + 'weight': 12, + 'value': '2601:642:500:e210:62f8:1dff:feb8:9477', + }, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'values': [ @@ -3913,29 +4235,44 @@ class TestDynamicRecords(TestCase): pools = dynamic.pools self.assertTrue(pools) - self.assertEqual({ - 'value': '2601:642:500:e210:62f8:1dff:feb8:9473', - 'weight': 1, - 'status': 'obey', - }, pools['one'].data['values'][0]) - self.assertEqual([{ - 'value': '2601:642:500:e210:62f8:1dff:feb8:9474', - 'weight': 1, - 'status': 'obey', - }, { - 'value': '2601:642:500:e210:62f8:1dff:feb8:9475', - 'weight': 1, - 'status': 'obey', - }], pools['two'].data['values']) - self.assertEqual([{ - 'weight': 10, - 'value': '2601:642:500:e210:62f8:1dff:feb8:9476', - 'status': 'obey', - }, { - 'weight': 12, - 'value': '2601:642:500:e210:62f8:1dff:feb8:9477', - 'status': 'obey', - }], pools['three'].data['values']) + self.assertEqual( + { + 'value': '2601:642:500:e210:62f8:1dff:feb8:9473', + 'weight': 1, + 'status': 'obey', + }, + pools['one'].data['values'][0], + ) + self.assertEqual( + [ + { + 'value': '2601:642:500:e210:62f8:1dff:feb8:9474', + 'weight': 1, + 'status': 'obey', + }, + { + 'value': '2601:642:500:e210:62f8:1dff:feb8:9475', + 'weight': 1, + 'status': 'obey', + }, + ], + pools['two'].data['values'], + ) + self.assertEqual( + [ + { + 'weight': 10, + 'value': '2601:642:500:e210:62f8:1dff:feb8:9476', + 'status': 'obey', + }, + { + 'weight': 12, + 'value': '2601:642:500:e210:62f8:1dff:feb8:9477', + 'status': 'obey', + }, + ], + pools['three'].data['values'], + ) rules = dynamic.rules self.assertTrue(rules) @@ -3945,35 +4282,20 @@ class TestDynamicRecords(TestCase): cname_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': 'one.cname.target.', - }], - }, - 'two': { - 'values': [{ - 'value': 'two.cname.target.', - }], - }, + 'one': {'values': [{'value': 'one.cname.target.'}]}, + 'two': {'values': [{'value': 'two.cname.target.'}]}, 'three': { - 'values': [{ - 'weight': 12, - 'value': 'three-1.cname.target.', - }, { - 'weight': 32, - 'value': 'three-2.cname.target.', - }] + 'values': [ + {'weight': 12, 'value': 'three-1.cname.target.'}, + {'weight': 32, 'value': 'three-2.cname.target.'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'value': 'cname.target.', @@ -3988,25 +4310,29 @@ class TestDynamicRecords(TestCase): pools = dynamic.pools self.assertTrue(pools) - self.assertEqual({ - 'value': 'one.cname.target.', - 'weight': 1, - 'status': 'obey', - }, pools['one'].data['values'][0]) - self.assertEqual({ - 'value': 'two.cname.target.', - 'weight': 1, - 'status': 'obey', - }, pools['two'].data['values'][0]) - self.assertEqual([{ - 'value': 'three-1.cname.target.', - 'weight': 12, - 'status': 'obey', - }, { - 'value': 'three-2.cname.target.', - 'weight': 32, - 'status': 'obey', - }], pools['three'].data['values']) + self.assertEqual( + {'value': 'one.cname.target.', 'weight': 1, 'status': 'obey'}, + pools['one'].data['values'][0], + ) + self.assertEqual( + {'value': 'two.cname.target.', 'weight': 1, 'status': 'obey'}, + pools['two'].data['values'][0], + ) + self.assertEqual( + [ + { + 'value': 'three-1.cname.target.', + 'weight': 12, + 'status': 'obey', + }, + { + 'value': 'three-2.cname.target.', + 'weight': 32, + 'status': 'obey', + }, + ], + pools['three'].data['values'], + ) rules = dynamic.rules self.assertTrue(rules) @@ -4015,117 +4341,86 @@ class TestDynamicRecords(TestCase): def test_dynamic_validation(self): # Missing pools a_data = { - 'dynamic': { - 'rules': [{ - 'pool': 'one', - }], - }, + 'dynamic': {'rules': [{'pool': 'one'}]}, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['missing pools', 'rule 1 undefined pool "one"'], - ctx.exception.reasons) + self.assertEqual( + ['missing pools', 'rule 1 undefined pool "one"'], + ctx.exception.reasons, + ) # Empty pools a_data = { - 'dynamic': { - 'pools': { - }, - 'rules': [{ - 'pool': 'one', - }], - }, + 'dynamic': {'pools': {}, 'rules': [{'pool': 'one'}]}, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['missing pools', 'rule 1 undefined pool "one"'], - ctx.exception.reasons) + self.assertEqual( + ['missing pools', 'rule 1 undefined pool "one"'], + ctx.exception.reasons, + ) # pools not a dict a_data = { - 'dynamic': { - 'pools': [], - 'rules': [{ - 'pool': 'one', - }], - }, + 'dynamic': {'pools': [], 'rules': [{'pool': 'one'}]}, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['pools must be a dict', - 'rule 1 undefined pool "one"'], - ctx.exception.reasons) + self.assertEqual( + ['pools must be a dict', 'rule 1 undefined pool "one"'], + ctx.exception.reasons, + ) # Invalid addresses a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': 'this-aint-right', - }], - }, + 'one': {'values': [{'value': 'this-aint-right'}]}, 'two': { 'fallback': 'one', - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': 'nor-is-this', - }] + 'values': [ + {'value': '4.4.4.4'}, + {'value': 'nor-is-this'}, + ], }, 'three': { 'fallback': 'two', - 'values': [{ - 'weight': 1, - 'value': '5.5.5.5', - }, { - 'weight': 2, - 'value': 'yet-another-bad-one', - }], + 'values': [ + {'weight': 1, 'value': '5.5.5.5'}, + {'weight': 2, 'value': 'yet-another-bad-one'}, + ], }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'invalid IPv4 address "this-aint-right"', - 'invalid IPv4 address "yet-another-bad-one"', - 'invalid IPv4 address "nor-is-this"', - ], ctx.exception.reasons) + self.assertEqual( + [ + 'invalid IPv4 address "this-aint-right"', + 'invalid IPv4 address "yet-another-bad-one"', + 'invalid IPv4 address "nor-is-this"', + ], + ctx.exception.reasons, + ) # missing value(s) a_data = { @@ -4133,43 +4428,30 @@ class TestDynamicRecords(TestCase): 'pools': { 'one': {}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, 'three': { - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 2, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 2, 'value': '7.7.7.7'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['pool "one" is missing values'], - ctx.exception.reasons) + self.assertEqual( + ['pool "one" is missing values'], ctx.exception.reasons + ) # pool value not a dict a_data = { @@ -4177,43 +4459,28 @@ class TestDynamicRecords(TestCase): 'pools': { 'one': '', 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, 'three': { - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 2, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 2, 'value': '7.7.7.7'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['pool "one" must be a dict'], - ctx.exception.reasons) + self.assertEqual(['pool "one" must be a dict'], ctx.exception.reasons) # empty pool value a_data = { @@ -4221,216 +4488,147 @@ class TestDynamicRecords(TestCase): 'pools': { 'one': {}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, 'three': { - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 2, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 2, 'value': '7.7.7.7'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['pool "one" is missing values'], - ctx.exception.reasons) + self.assertEqual( + ['pool "one" is missing values'], ctx.exception.reasons + ) # invalid int weight a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, 'three': { - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 101, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 101, 'value': '7.7.7.7'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['invalid weight "101" in pool "three" value 2'], - ctx.exception.reasons) + self.assertEqual( + ['invalid weight "101" in pool "three" value 2'], + ctx.exception.reasons, + ) # invalid non-int weight a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, 'three': { - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 'foo', - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 'foo', 'value': '7.7.7.7'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['invalid weight "foo" in pool "three" value 2'], - ctx.exception.reasons) + self.assertEqual( + ['invalid weight "foo" in pool "three" value 2'], + ctx.exception.reasons, + ) # single value with weight!=1 a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'weight': 12, - 'value': '6.6.6.6', - }], - }, + 'one': {'values': [{'weight': 12, 'value': '6.6.6.6'}]} }, - 'rules': [{ - 'pool': 'one', - }], + 'rules': [{'pool': 'one'}], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['pool "one" has single value with weight!=1'], - ctx.exception.reasons) + self.assertEqual( + ['pool "one" has single value with weight!=1'], + ctx.exception.reasons, + ) # invalid fallback a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }], - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { 'fallback': 'invalid', - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}], }, 'three': { 'fallback': 'two', - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 5, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 5, 'value': '7.7.7.7'}, + ], }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['undefined fallback "invalid" for pool "two"'], - ctx.exception.reasons) + self.assertEqual( + ['undefined fallback "invalid" for pool "two"'], + ctx.exception.reasons, + ) # fallback loop a_data = { @@ -4438,53 +4636,40 @@ class TestDynamicRecords(TestCase): 'pools': { 'one': { 'fallback': 'three', - 'values': [{ - 'value': '3.3.3.3', - }], + 'values': [{'value': '3.3.3.3'}], }, 'two': { 'fallback': 'one', - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}], }, 'three': { 'fallback': 'two', - 'values': [{ - 'weight': 1, - 'value': '6.6.6.6', - }, { - 'weight': 5, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1, 'value': '6.6.6.6'}, + {'weight': 5, 'value': '7.7.7.7'}, + ], }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'loop in pool fallbacks: one -> three -> two', - 'loop in pool fallbacks: three -> two -> one', - 'loop in pool fallbacks: two -> one -> three' - ], ctx.exception.reasons) + self.assertEqual( + [ + 'loop in pool fallbacks: one -> three -> two', + 'loop in pool fallbacks: three -> two -> one', + 'loop in pool fallbacks: two -> one -> three', + ], + ctx.exception.reasons, + ) # multiple pool problems a_data = { @@ -4492,438 +4677,276 @@ class TestDynamicRecords(TestCase): 'pools': { 'one': '', 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': 'blip', - }] + 'values': [{'value': '4.4.4.4'}, {'value': 'blip'}] }, 'three': { - 'values': [{ - 'weight': 1, - }, { - 'weight': 5000, - 'value': '7.7.7.7', - }], + 'values': [ + {'weight': 1}, + {'weight': 5000, 'value': '7.7.7.7'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'pool "one" must be a dict', - 'missing value in pool "three" value 1', - 'invalid weight "5000" in pool "three" value 2', - 'invalid IPv4 address "blip"', - ], ctx.exception.reasons) + self.assertEqual( + [ + 'pool "one" must be a dict', + 'missing value in pool "three" value 1', + 'invalid weight "5000" in pool "three" value 2', + 'invalid IPv4 address "blip"', + ], + ctx.exception.reasons, + ) # missing rules, and unused pools a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, - }, + } }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'missing rules', - 'unused pools: "one", "two"', - ], ctx.exception.reasons) + self.assertEqual( + ['missing rules', 'unused pools: "one", "two"'], + ctx.exception.reasons, + ) # empty rules a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, 'rules': [], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'missing rules', - 'unused pools: "one", "two"', - ], ctx.exception.reasons) + self.assertEqual( + ['missing rules', 'unused pools: "one", "two"'], + ctx.exception.reasons, + ) # rules not a list/tuple a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, 'rules': {}, }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'rules must be a list', - 'unused pools: "one", "two"', - ], ctx.exception.reasons) + self.assertEqual( + ['rules must be a list', 'unused pools: "one", "two"'], + ctx.exception.reasons, + ) # rule without pool a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }], - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['NA-US-CA'], - }, { - 'pool': 'one', - }], + 'rules': [{'geos': ['NA-US-CA']}, {'pool': 'one'}], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'rule 1 missing pool', - 'unused pools: "two"', - ], ctx.exception.reasons) + self.assertEqual( + ['rule 1 missing pool', 'unused pools: "two"'], + ctx.exception.reasons, + ) # rule with non-string pools a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['NA-US-CA'], - 'pool': [], - }, { - 'pool': 'one', - }], + 'rules': [{'geos': ['NA-US-CA'], 'pool': []}, {'pool': 'one'}], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - 'rule 1 invalid pool "[]"', - 'unused pools: "two"', - ], ctx.exception.reasons) + self.assertEqual( + ['rule 1 invalid pool "[]"', 'unused pools: "two"'], + ctx.exception.reasons, + ) # rule references non-existent pool a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['NA-US-CA'], - 'pool': 'non-existent', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['NA-US-CA'], 'pool': 'non-existent'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual([ - "rule 1 undefined pool \"non-existent\"", - 'unused pools: "two"', - ], ctx.exception.reasons) + self.assertEqual( + ["rule 1 undefined pool \"non-existent\"", 'unused pools: "two"'], + ctx.exception.reasons, + ) # rule with invalid geos a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': 'NA-US-CA', - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [{'geos': 'NA-US-CA', 'pool': 'two'}, {'pool': 'one'}], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['rule 1 geos must be a list'], - ctx.exception.reasons) + self.assertEqual(['rule 1 geos must be a list'], ctx.exception.reasons) # rule with invalid geo a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['invalid'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['invalid'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['rule 1 unknown continent code "invalid"'], - ctx.exception.reasons) + self.assertEqual( + ['rule 1 unknown continent code "invalid"'], ctx.exception.reasons + ) # multiple default rules a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [{'pool': 'two'}, {'pool': 'one'}], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['rule 2 duplicate default'], - ctx.exception.reasons) + self.assertEqual(['rule 2 duplicate default'], ctx.exception.reasons) # repeated pool in rules a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['EU'], - 'pool': 'two', - }, { - 'geos': ['AF'], - 'pool': 'one', - }, { - 'geos': ['OC'], - 'pool': 'one', - }], + 'rules': [ + {'geos': ['EU'], 'pool': 'two'}, + {'geos': ['AF'], 'pool': 'one'}, + {'geos': ['OC'], 'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['rule 3 invalid, target pool "one" reused'], - ctx.exception.reasons) + self.assertEqual( + ['rule 3 invalid, target pool "one" reused'], ctx.exception.reasons + ) # Repeated pool is OK if later one is a default a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['EU-GB'], - 'pool': 'one', - }, { - 'geos': ['EU'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [ + {'geos': ['EU-GB'], 'pool': 'one'}, + {'geos': ['EU'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } # This should be valid, no exception Record.new(self.zone, 'bad', a_data) @@ -4932,16 +4955,9 @@ class TestDynamicRecords(TestCase): a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '2.2.2.2', - 'status': 'none', - }], - }, + 'one': {'values': [{'value': '2.2.2.2', 'status': 'none'}]} }, - 'rules': [{ - 'pool': 'one', - }], + 'rules': [{'pool': 'one'}], }, 'ttl': 60, 'type': 'A', @@ -4955,136 +4971,98 @@ class TestDynamicRecords(TestCase): # Missing pools a_data = { 'dynamic': { - 'rules': [{ - 'geos': ['EU'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [{'geos': ['EU'], 'pool': 'two'}, {'pool': 'one'}] }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } a = Record.new(self.zone, 'bad', a_data, lenient=True) - self.assertEqual({ - 'pools': {}, - 'rules': a_data['dynamic']['rules'], - }, a._data()['dynamic']) + self.assertEqual( + {'pools': {}, 'rules': a_data['dynamic']['rules']}, + a._data()['dynamic'], + ) # Missing rule a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - 'weight': 2, - }] + 'values': [ + {'value': '4.4.4.4'}, + {'value': '5.5.5.5', 'weight': 2}, + ] }, - }, + } }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } a = Record.new(self.zone, 'bad', a_data, lenient=True) - self.assertEqual({ - 'pools': { - 'one': { - 'fallback': None, - 'values': [{ - 'value': '3.3.3.3', - 'weight': 1, - 'status': 'obey', - }] - }, - 'two': { - 'fallback': None, - 'values': [{ - 'value': '4.4.4.4', - 'weight': 1, - 'status': 'obey', - }, { - 'value': '5.5.5.5', - 'weight': 2, - 'status': 'obey', - }] + self.assertEqual( + { + 'pools': { + 'one': { + 'fallback': None, + 'values': [ + {'value': '3.3.3.3', 'weight': 1, 'status': 'obey'} + ], + }, + 'two': { + 'fallback': None, + 'values': [ + {'value': '4.4.4.4', 'weight': 1, 'status': 'obey'}, + {'value': '5.5.5.5', 'weight': 2, 'status': 'obey'}, + ], + }, }, + 'rules': [], }, - 'rules': [], - }, a._data()['dynamic']) + a._data()['dynamic'], + ) # rule without pool a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - 'weight': 2, - }] + 'values': [ + {'value': '4.4.4.4'}, + {'value': '5.5.5.5', 'weight': 2}, + ] }, }, - 'rules': [{ - 'geos': ['EU'], - 'pool': 'two', - }, { - }], + 'rules': [{'geos': ['EU'], 'pool': 'two'}, {}], }, 'ttl': 60, 'type': 'A', - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } a = Record.new(self.zone, 'bad', a_data, lenient=True) - self.assertEqual({ - 'pools': { - 'one': { - 'fallback': None, - 'values': [{ - 'value': '3.3.3.3', - 'weight': 1, - 'status': 'obey', - }] - }, - 'two': { - 'fallback': None, - 'values': [{ - 'value': '4.4.4.4', - 'weight': 1, - 'status': 'obey', - }, { - 'value': '5.5.5.5', - 'weight': 2, - 'status': 'obey', - }] + self.assertEqual( + { + 'pools': { + 'one': { + 'fallback': None, + 'values': [ + {'value': '3.3.3.3', 'weight': 1, 'status': 'obey'} + ], + }, + 'two': { + 'fallback': None, + 'values': [ + {'value': '4.4.4.4', 'weight': 1, 'status': 'obey'}, + {'value': '5.5.5.5', 'weight': 2, 'status': 'obey'}, + ], + }, }, + 'rules': a_data['dynamic']['rules'], }, - 'rules': a_data['dynamic']['rules'], - }, a._data()['dynamic']) + a._data()['dynamic'], + ) def test_dynamic_changes(self): simple = SimpleProvider() @@ -5093,31 +5071,15 @@ class TestDynamicRecords(TestCase): a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['EU'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [{'geos': ['EU'], 'pool': 'two'}, {'pool': 'one'}], }, 'ttl': 60, - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } a = ARecord(self.zone, 'weighted', a_data) dup = ARecord(self.zone, 'weighted', a_data) @@ -5125,63 +5087,33 @@ class TestDynamicRecords(TestCase): b_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - 'weight': 2, - }, { - 'value': '5.5.5.5', - }] + 'values': [ + {'value': '4.4.4.4', 'weight': 2}, + {'value': '5.5.5.5'}, + ] }, }, - 'rules': [{ - 'geos': ['EU'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [{'geos': ['EU'], 'pool': 'two'}, {'pool': 'one'}], }, 'ttl': 60, - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } b = ARecord(self.zone, 'weighted', b_data) c_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }] - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { - 'values': [{ - 'value': '4.4.4.4', - }, { - 'value': '5.5.5.5', - }] + 'values': [{'value': '4.4.4.4'}, {'value': '5.5.5.5'}] }, }, - 'rules': [{ - 'geos': ['NA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], + 'rules': [{'geos': ['NA'], 'pool': 'two'}, {'pool': 'one'}], }, 'ttl': 60, - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } c = ARecord(self.zone, 'weighted', c_data) @@ -5219,88 +5151,50 @@ class TestDynamicRecords(TestCase): a_data = { 'dynamic': { 'pools': { - 'one': { - 'values': [{ - 'value': '3.3.3.3', - }], - }, + 'one': {'values': [{'value': '3.3.3.3'}]}, 'two': { # Testing out of order value sorting here - 'values': [{ - 'value': '5.5.5.5', - }, { - 'value': '4.4.4.4', - }], + 'values': [{'value': '5.5.5.5'}, {'value': '4.4.4.4'}] }, 'three': { - 'values': [{ - 'weight': 10, - 'value': '4.4.4.4', - }, { - 'weight': 12, - 'value': '5.5.5.5', - }], + 'values': [ + {'weight': 10, 'value': '4.4.4.4'}, + {'weight': 12, 'value': '5.5.5.5'}, + ] }, }, - 'rules': [{ - 'geos': ['AF', 'EU'], - 'pool': 'three', - }, { - 'geos': ['NA-US-CA'], - 'pool': 'two', - }, { - 'pool': 'one', - }], - }, - 'geo': { - 'NA': ['1.2.3.5'], - 'NA-US': ['1.2.3.5', '1.2.3.6'] + 'rules': [ + {'geos': ['AF', 'EU'], 'pool': 'three'}, + {'geos': ['NA-US-CA'], 'pool': 'two'}, + {'pool': 'one'}, + ], }, + 'geo': {'NA': ['1.2.3.5'], 'NA-US': ['1.2.3.5', '1.2.3.6']}, 'type': 'A', 'ttl': 60, - 'values': [ - '1.1.1.1', - '2.2.2.2', - ], + 'values': ['1.1.1.1', '2.2.2.2'], } with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, 'bad', a_data) - self.assertEqual(['"dynamic" record with "geo" content'], - ctx.exception.reasons) + self.assertEqual( + ['"dynamic" record with "geo" content'], ctx.exception.reasons + ) def test_dynamic_eqs(self): - pool_one = _DynamicPool('one', { - 'values': [{ - 'value': '1.2.3.4', - }], - }) - pool_two = _DynamicPool('two', { - 'values': [{ - 'value': '1.2.3.5', - }], - }) + pool_one = _DynamicPool('one', {'values': [{'value': '1.2.3.4'}]}) + pool_two = _DynamicPool('two', {'values': [{'value': '1.2.3.5'}]}) self.assertEqual(pool_one, pool_one) self.assertNotEqual(pool_one, pool_two) self.assertNotEqual(pool_one, 42) - pools = { - 'one': pool_one, - 'two': pool_two, - } - rule_one = _DynamicRule(0, { - 'pool': 'one', - }) - rule_two = _DynamicRule(1, { - 'pool': 'two', - }) + pools = {'one': pool_one, 'two': pool_two} + rule_one = _DynamicRule(0, {'pool': 'one'}) + rule_two = _DynamicRule(1, {'pool': 'two'}) self.assertEqual(rule_one, rule_one) self.assertNotEqual(rule_one, rule_two) self.assertNotEqual(rule_one, 42) - rules = [ - rule_one, - rule_two, - ] + rules = [rule_one, rule_two] dynamic = _Dynamic(pools, rules) other = _Dynamic({}, []) @@ -5311,26 +5205,30 @@ class TestDynamicRecords(TestCase): class TestChanges(TestCase): zone = Zone('unit.tests.', []) - record_a_1 = Record.new(zone, '1', { - 'type': 'A', - 'ttl': 30, - 'value': '1.2.3.4', - }) - record_a_2 = Record.new(zone, '2', { - 'type': 'A', - 'ttl': 30, - 'value': '1.2.3.4', - }) - record_aaaa_1 = Record.new(zone, '1', { - 'type': 'AAAA', - 'ttl': 30, - 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', - }) - record_aaaa_2 = Record.new(zone, '2', { - 'type': 'AAAA', - 'ttl': 30, - 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', - }) + record_a_1 = Record.new( + zone, '1', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'} + ) + record_a_2 = Record.new( + zone, '2', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'} + ) + record_aaaa_1 = Record.new( + zone, + '1', + { + 'type': 'AAAA', + 'ttl': 30, + 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', + }, + ) + record_aaaa_2 = Record.new( + zone, + '2', + { + 'type': 'AAAA', + 'ttl': 30, + 'value': '2601:644:500:e210:62f8:1dff:feb8:947a', + }, + ) def test_sort_same_change_type(self): # expect things to be ordered by name and type since all the change @@ -5341,12 +5239,15 @@ class TestChanges(TestCase): Create(self.record_a_1), Create(self.record_aaaa_2), ] - self.assertEqual([ - Create(self.record_a_1), - Create(self.record_aaaa_1), - Create(self.record_a_2), - Create(self.record_aaaa_2), - ], sorted(changes)) + self.assertEqual( + [ + Create(self.record_a_1), + Create(self.record_aaaa_1), + Create(self.record_a_2), + Create(self.record_aaaa_2), + ], + sorted(changes), + ) def test_sort_same_different_type(self): # this time the change type is the deciding factor, deletes come before @@ -5366,17 +5267,20 @@ class TestChanges(TestCase): Create(self.record_a_2), Delete(self.record_a_2), ] - self.assertEqual([ - Delete(self.record_a_1), - Delete(self.record_aaaa_1), - Delete(self.record_a_2), - Delete(self.record_aaaa_2), - Create(self.record_a_1), - Create(self.record_aaaa_1), - Create(self.record_a_2), - Create(self.record_aaaa_2), - Update(self.record_a_1, self.record_a_1), - Update(self.record_aaaa_1, self.record_aaaa_1), - Update(self.record_a_2, self.record_a_2), - Update(self.record_aaaa_2, self.record_aaaa_2), - ], sorted(changes)) + self.assertEqual( + [ + Delete(self.record_a_1), + Delete(self.record_aaaa_1), + Delete(self.record_a_2), + Delete(self.record_aaaa_2), + Create(self.record_a_1), + Create(self.record_aaaa_1), + Create(self.record_a_2), + Create(self.record_aaaa_2), + Update(self.record_a_1, self.record_a_1), + Update(self.record_aaaa_1, self.record_aaaa_1), + Update(self.record_a_2, self.record_a_2), + Update(self.record_aaaa_2, self.record_aaaa_2), + ], + sorted(changes), + ) diff --git a/tests/test_octodns_record_geo.py b/tests/test_octodns_record_geo.py index 06f1609..16745ac 100644 --- a/tests/test_octodns_record_geo.py +++ b/tests/test_octodns_record_geo.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase @@ -11,7 +15,6 @@ from octodns.record.geo import GeoCodes class TestRecordGeoCodes(TestCase): - def test_validate(self): prefix = 'xyz ' @@ -21,53 +24,81 @@ class TestRecordGeoCodes(TestCase): self.assertEqual([], GeoCodes.validate('NA-US-OR', prefix)) # Just plain bad - self.assertEqual(['xyz invalid geo code "XX-YY-ZZ-AA"'], - GeoCodes.validate('XX-YY-ZZ-AA', prefix)) - self.assertEqual(['xyz unknown continent code "X-Y-Z"'], - GeoCodes.validate('X-Y-Z', prefix)) - self.assertEqual(['xyz unknown continent code "XXX-Y-Z"'], - GeoCodes.validate('XXX-Y-Z', prefix)) + self.assertEqual( + ['xyz invalid geo code "XX-YY-ZZ-AA"'], + GeoCodes.validate('XX-YY-ZZ-AA', prefix), + ) + self.assertEqual( + ['xyz unknown continent code "X-Y-Z"'], + GeoCodes.validate('X-Y-Z', prefix), + ) + self.assertEqual( + ['xyz unknown continent code "XXX-Y-Z"'], + GeoCodes.validate('XXX-Y-Z', prefix), + ) # Bad continent - self.assertEqual(['xyz unknown continent code "XX"'], - GeoCodes.validate('XX', prefix)) + self.assertEqual( + ['xyz unknown continent code "XX"'], GeoCodes.validate('XX', prefix) + ) # Bad continent good country - self.assertEqual(['xyz unknown continent code "XX-US"'], - GeoCodes.validate('XX-US', prefix)) + self.assertEqual( + ['xyz unknown continent code "XX-US"'], + GeoCodes.validate('XX-US', prefix), + ) # Bad continent good country and province - self.assertEqual(['xyz unknown continent code "XX-US-OR"'], - GeoCodes.validate('XX-US-OR', prefix)) + self.assertEqual( + ['xyz unknown continent code "XX-US-OR"'], + GeoCodes.validate('XX-US-OR', prefix), + ) # Bad country, good continent - self.assertEqual(['xyz unknown country code "NA-XX"'], - GeoCodes.validate('NA-XX', prefix)) + self.assertEqual( + ['xyz unknown country code "NA-XX"'], + GeoCodes.validate('NA-XX', prefix), + ) # Bad country, good continent and state - self.assertEqual(['xyz unknown country code "NA-XX-OR"'], - GeoCodes.validate('NA-XX-OR', prefix)) + self.assertEqual( + ['xyz unknown country code "NA-XX-OR"'], + GeoCodes.validate('NA-XX-OR', prefix), + ) # Good country, good continent, but bad match - self.assertEqual(['xyz unknown country code "NA-GB"'], - GeoCodes.validate('NA-GB', prefix)) + self.assertEqual( + ['xyz unknown country code "NA-GB"'], + GeoCodes.validate('NA-GB', prefix), + ) # Bad province code, good continent and country - self.assertEqual(['xyz unknown province code "NA-US-XX"'], - GeoCodes.validate('NA-US-XX', prefix)) + self.assertEqual( + ['xyz unknown province code "NA-US-XX"'], + GeoCodes.validate('NA-US-XX', prefix), + ) def test_parse(self): - self.assertEqual({ - 'continent_code': 'NA', - 'country_code': None, - 'province_code': None, - }, GeoCodes.parse('NA')) - self.assertEqual({ - 'continent_code': 'NA', - 'country_code': 'US', - 'province_code': None, - }, GeoCodes.parse('NA-US')) - self.assertEqual({ - 'continent_code': 'NA', - 'country_code': 'US', - 'province_code': 'CA', - }, GeoCodes.parse('NA-US-CA')) + self.assertEqual( + { + 'continent_code': 'NA', + 'country_code': None, + 'province_code': None, + }, + GeoCodes.parse('NA'), + ) + self.assertEqual( + { + 'continent_code': 'NA', + 'country_code': 'US', + 'province_code': None, + }, + GeoCodes.parse('NA-US'), + ) + self.assertEqual( + { + 'continent_code': 'NA', + 'country_code': 'US', + 'province_code': 'CA', + }, + GeoCodes.parse('NA-US-CA'), + ) def test_country_to_code(self): self.assertEqual('NA-US', GeoCodes.country_to_code('US')) diff --git a/tests/test_octodns_source_axfr.py b/tests/test_octodns_source_axfr.py index bb548b1..c878753 100644 --- a/tests/test_octodns_source_axfr.py +++ b/tests/test_octodns_source_axfr.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) import dns.zone from dns.exception import DNSException @@ -13,8 +17,12 @@ from shutil import copyfile from unittest import TestCase from unittest.mock import patch -from octodns.source.axfr import AxfrSource, AxfrSourceZoneTransferFailed, \ - ZoneFileSource, ZoneFileSourceLoadFailure +from octodns.source.axfr import ( + AxfrSource, + AxfrSourceZoneTransferFailed, + ZoneFileSource, + ZoneFileSourceLoadFailure, +) from octodns.zone import Zone from octodns.record import ValidationError @@ -22,17 +30,15 @@ from octodns.record import ValidationError class TestAxfrSource(TestCase): source = AxfrSource('test', 'localhost') - forward_zonefile = dns.zone.from_file('./tests/zones/unit.tests.tst', - 'unit.tests', relativize=False) + forward_zonefile = dns.zone.from_file( + './tests/zones/unit.tests.tst', 'unit.tests', relativize=False + ) @patch('dns.zone.from_xfr') def test_populate(self, from_xfr_mock): got = Zone('unit.tests.', []) - from_xfr_mock.side_effect = [ - self.forward_zonefile, - DNSException - ] + from_xfr_mock.side_effect = [self.forward_zonefile, DNSException] self.source.populate(got) self.assertEqual(16, len(got.records)) @@ -40,8 +46,7 @@ class TestAxfrSource(TestCase): with self.assertRaises(AxfrSourceZoneTransferFailed) as ctx: zone = Zone('unit.tests.', []) self.source.populate(zone) - self.assertEqual('Unable to Perform Zone Transfer', - str(ctx.exception)) + self.assertEqual('Unable to Perform Zone Transfer', str(ctx.exception)) class TestZoneFileSource(TestCase): @@ -65,8 +70,10 @@ class TestZoneFileSource(TestCase): # It did so we need to skip this test, that means windows won't # have full code coverage, but skipping the test is going out of # our way enough for a os-specific/oddball case. - self.skipTest('Unable to create unit.tests. (ending with .) so ' - 'skipping default filename testing.') + self.skipTest( + 'Unable to create unit.tests. (ending with .) so ' + 'skipping default filename testing.' + ) source = ZoneFileSource('test', './tests/zones') # Load zonefiles without a specified file extension @@ -97,16 +104,19 @@ class TestZoneFileSource(TestCase): with self.assertRaises(ZoneFileSourceLoadFailure) as ctx: zone = Zone('invalid.zone.', []) self.source.populate(zone) - self.assertEqual('The DNS zone has no NS RRset at its origin.', - str(ctx.exception)) + self.assertEqual( + 'The DNS zone has no NS RRset at its origin.', str(ctx.exception) + ) # Records are not to RFC (lenient=False) with self.assertRaises(ValidationError) as ctx: zone = Zone('invalid.records.', []) self.source.populate(zone) - self.assertEqual('Invalid record _invalid.invalid.records.\n' - ' - invalid name for SRV record', - str(ctx.exception)) + self.assertEqual( + 'Invalid record _invalid.invalid.records.\n' + ' - invalid name for SRV record', + str(ctx.exception), + ) # Records are not to RFC, but load anyhow (lenient=True) invalid = Zone('invalid.records.', []) diff --git a/tests/test_octodns_source_envvar.py b/tests/test_octodns_source_envvar.py index 775b541..cb40618 100644 --- a/tests/test_octodns_source_envvar.py +++ b/tests/test_octodns_source_envvar.py @@ -7,7 +7,6 @@ from octodns.zone import Zone class TestEnvVarSource(TestCase): - def test_read_variable(self): envvar = 'OCTODNS_TEST_ENVIRONMENT_VARIABLE' source = EnvVarSource('testid', envvar, 'recordname', ttl=120) diff --git a/tests/test_octodns_source_tinydns.py b/tests/test_octodns_source_tinydns.py index bfc2cf1..140ae42 100644 --- a/tests/test_octodns_source_tinydns.py +++ b/tests/test_octodns_source_tinydns.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase @@ -24,103 +28,96 @@ class TestTinyDnsFileSource(TestCase): expected = Zone('example.com.', []) for name, data in ( - ('', { - 'type': 'A', - 'ttl': 30, - 'values': ['10.2.3.4', '10.2.3.5'], - }), - ('', { - 'type': 'NS', - 'ttl': 3600, - 'values': ['ns1.ns.com.', 'ns2.ns.com.'], - }), - ('sub', { - 'type': 'NS', - 'ttl': 30, - 'values': ['ns3.ns.com.', 'ns4.ns.com.'], - }), - ('www', { - 'type': 'A', - 'ttl': 3600, - 'value': '10.2.3.6', - }), - ('cname', { - 'type': 'CNAME', - 'ttl': 3600, - 'value': 'www.example.com.', - }), - ('some-host-abc123', { - 'type': 'A', - 'ttl': 1800, - 'value': '10.2.3.7', - }), - ('has-dup-def123', { - 'type': 'A', - 'ttl': 3600, - 'value': '10.2.3.8', - }), - ('www.sub', { - 'type': 'A', - 'ttl': 3600, - 'value': '1.2.3.4', - }), - ('has-dup-def456', { - 'type': 'A', - 'ttl': 3600, - 'value': '10.2.3.8', - }), - ('', { - 'type': 'MX', - 'ttl': 3600, - 'values': [{ - 'preference': 10, - 'exchange': 'smtp-1-host.example.com.', - }, { - 'preference': 20, - 'exchange': 'smtp-2-host.example.com.', - }] - }), - ('smtp', { - 'type': 'MX', - 'ttl': 1800, - 'values': [{ - 'preference': 30, - 'exchange': 'smtp-1-host.example.com.', - }, { - 'preference': 40, - 'exchange': 'smtp-2-host.example.com.', - }] - }), - ('', { - 'type': 'TXT', - 'ttl': 300, - 'value': 'test TXT', - }), - ('colon', { - 'type': 'TXT', - 'ttl': 300, - 'value': 'test : TXT', - }), - ('nottl', { - 'type': 'TXT', - 'ttl': 3600, - 'value': 'nottl test TXT', - }), - ('ipv6-3', { - 'type': 'AAAA', - 'ttl': 300, - 'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5742', - }), - ('ipv6-6', { - 'type': 'AAAA', - 'ttl': 3600, - 'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5743', - }), - ('semicolon', { - 'type': 'TXT', - 'ttl': 300, - 'value': 'v=DKIM1\\; k=rsa\\; p=blah', - }), + ('', {'type': 'A', 'ttl': 30, 'values': ['10.2.3.4', '10.2.3.5']}), + ( + '', + { + 'type': 'NS', + 'ttl': 3600, + 'values': ['ns1.ns.com.', 'ns2.ns.com.'], + }, + ), + ( + 'sub', + { + 'type': 'NS', + 'ttl': 30, + 'values': ['ns3.ns.com.', 'ns4.ns.com.'], + }, + ), + ('www', {'type': 'A', 'ttl': 3600, 'value': '10.2.3.6'}), + ( + 'cname', + {'type': 'CNAME', 'ttl': 3600, 'value': 'www.example.com.'}, + ), + ( + 'some-host-abc123', + {'type': 'A', 'ttl': 1800, 'value': '10.2.3.7'}, + ), + ('has-dup-def123', {'type': 'A', 'ttl': 3600, 'value': '10.2.3.8'}), + ('www.sub', {'type': 'A', 'ttl': 3600, 'value': '1.2.3.4'}), + ('has-dup-def456', {'type': 'A', 'ttl': 3600, 'value': '10.2.3.8'}), + ( + '', + { + 'type': 'MX', + 'ttl': 3600, + 'values': [ + { + 'preference': 10, + 'exchange': 'smtp-1-host.example.com.', + }, + { + 'preference': 20, + 'exchange': 'smtp-2-host.example.com.', + }, + ], + }, + ), + ( + 'smtp', + { + 'type': 'MX', + 'ttl': 1800, + 'values': [ + { + 'preference': 30, + 'exchange': 'smtp-1-host.example.com.', + }, + { + 'preference': 40, + 'exchange': 'smtp-2-host.example.com.', + }, + ], + }, + ), + ('', {'type': 'TXT', 'ttl': 300, 'value': 'test TXT'}), + ('colon', {'type': 'TXT', 'ttl': 300, 'value': 'test : TXT'}), + ('nottl', {'type': 'TXT', 'ttl': 3600, 'value': 'nottl test TXT'}), + ( + 'ipv6-3', + { + 'type': 'AAAA', + 'ttl': 300, + 'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5742', + }, + ), + ( + 'ipv6-6', + { + 'type': 'AAAA', + 'ttl': 3600, + 'value': '2a02:1348:017c:d5d0:0024:19ff:fef3:5743', + }, + ), + ( + 'semicolon', + { + 'type': 'TXT', + 'ttl': 300, + 'value': 'v=DKIM1\\; k=rsa\\; p=blah', + }, + ), ): record = Record.new(expected, name, data) expected.add_record(record) @@ -135,11 +132,7 @@ class TestTinyDnsFileSource(TestCase): expected = Zone('asdf.subtest.com.', []) for name, data in ( - ('a3', { - 'type': 'A', - 'ttl': 3600, - 'values': ['10.2.3.7'], - }), + ('a3', {'type': 'A', 'ttl': 3600, 'values': ['10.2.3.7']}), ): record = Record.new(expected, name, data) expected.add_record(record) @@ -154,16 +147,8 @@ class TestTinyDnsFileSource(TestCase): expected = Zone('sub-asdf.subtest.com.', []) for name, data in ( - ('a1', { - 'type': 'A', - 'ttl': 3600, - 'values': ['10.2.3.5'], - }), - ('a2', { - 'type': 'A', - 'ttl': 3600, - 'values': ['10.2.3.6'], - }), + ('a1', {'type': 'A', 'ttl': 3600, 'values': ['10.2.3.5']}), + ('a2', {'type': 'A', 'ttl': 3600, 'values': ['10.2.3.6']}), ): record = Record.new(expected, name, data) expected.add_record(record) @@ -178,26 +163,24 @@ class TestTinyDnsFileSource(TestCase): expected = Zone('3.2.10.in-addr.arpa.', []) for name, data in ( - ('10', { - 'type': 'PTR', - 'ttl': 3600, - 'value': 'a-ptr.example.com.' - }), - ('11', { - 'type': 'PTR', - 'ttl': 30, - 'value': 'a-ptr-2.example.com.' - }), - ('8', { - 'type': 'PTR', - 'ttl': 3600, - 'value': 'has-dup-def123.example.com.' - }), - ('7', { - 'type': 'PTR', - 'ttl': 1800, - 'value': 'some-host-abc123.example.com.' - }), + ('10', {'type': 'PTR', 'ttl': 3600, 'value': 'a-ptr.example.com.'}), + ('11', {'type': 'PTR', 'ttl': 30, 'value': 'a-ptr-2.example.com.'}), + ( + '8', + { + 'type': 'PTR', + 'ttl': 3600, + 'value': 'has-dup-def123.example.com.', + }, + ), + ( + '7', + { + 'type': 'PTR', + 'ttl': 1800, + 'value': 'some-host-abc123.example.com.', + }, + ), ): record = Record.new(expected, name, data) expected.add_record(record) diff --git a/tests/test_octodns_yaml.py b/tests/test_octodns_yaml.py index 91adffc..58b4aae 100644 --- a/tests/test_octodns_yaml.py +++ b/tests/test_octodns_yaml.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from io import StringIO from unittest import TestCase @@ -13,58 +17,54 @@ from octodns.yaml import safe_dump, safe_load class TestYaml(TestCase): - def test_stuff(self): - self.assertEqual({ - 1: 'a', - 2: 'b', - '3': 'c', - 10: 'd', - '11': 'e', - }, safe_load(''' + self.assertEqual( + {1: 'a', 2: 'b', '3': 'c', 10: 'd', '11': 'e'}, + safe_load( + ''' 1: a 2: b '3': c 10: d '11': e -''')) +''' + ), + ) - self.assertEqual({ - '*.1.2': 'a', - '*.2.2': 'b', - '*.10.1': 'c', - '*.11.2': 'd', - }, safe_load(''' + self.assertEqual( + {'*.1.2': 'a', '*.2.2': 'b', '*.10.1': 'c', '*.11.2': 'd'}, + safe_load( + ''' '*.1.2': 'a' '*.2.2': 'b' '*.10.1': 'c' '*.11.2': 'd' -''')) +''' + ), + ) with self.assertRaises(ConstructorError) as ctx: - safe_load(''' + safe_load( + ''' '*.2.2': 'b' '*.1.2': 'a' '*.11.2': 'd' '*.10.1': 'c' -''') - self.assertTrue('keys out of order: expected *.1.2 got *.2.2 at' in - ctx.exception.problem) +''' + ) + self.assertTrue( + 'keys out of order: expected *.1.2 got *.2.2 at' + in ctx.exception.problem + ) buf = StringIO() - safe_dump({ - '*.1.1': 42, - '*.11.1': 43, - '*.2.1': 44, - }, buf) - self.assertEqual("---\n'*.1.1': 42\n'*.2.1': 44\n'*.11.1': 43\n", - buf.getvalue()) + safe_dump({'*.1.1': 42, '*.11.1': 43, '*.2.1': 44}, buf) + self.assertEqual( + "---\n'*.1.1': 42\n'*.2.1': 44\n'*.11.1': 43\n", buf.getvalue() + ) # hex sorting isn't ideal, not treated as hex, this make sure we don't # change the behavior buf = StringIO() - safe_dump({ - '45a03129': 42, - '45a0392a': 43, - }, buf) + safe_dump({'45a03129': 42, '45a0392a': 43}, buf) self.assertEqual("---\n45a0392a: 43\n45a03129: 42\n", buf.getvalue()) diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index 9385984..b884397 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -2,21 +2,35 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase -from octodns.record import ARecord, AaaaRecord, Create, Delete, NsRecord, \ - Record, Update -from octodns.zone import DuplicateRecordException, InvalidNodeException, \ - SubzoneRecordException, Zone +from octodns.record import ( + ARecord, + AaaaRecord, + Create, + Delete, + NsRecord, + Record, + Update, +) +from octodns.zone import ( + DuplicateRecordException, + InvalidNodeException, + SubzoneRecordException, + Zone, +) from helpers import SimpleProvider class TestZone(TestCase): - def test_lowering(self): zone = Zone('UniT.TEsTs.', []) self.assertEqual('unit.tests.', zone.name) @@ -47,8 +61,9 @@ class TestZone(TestCase): # Can't add record with same name & type with self.assertRaises(DuplicateRecordException) as ctx: zone.add_record(a) - self.assertEqual('Duplicate record a.unit.tests., type A', - str(ctx.exception)) + self.assertEqual( + 'Duplicate record a.unit.tests., type A', str(ctx.exception) + ) self.assertEqual(zone.records, set([a])) # can add duplicate with replace=True @@ -108,7 +123,6 @@ class TestZone(TestCase): update.__repr__() def test_unsupporting(self): - class NoAaaaProvider(object): id = 'no-aaaa' SUPPORTS_GEO = False @@ -144,21 +158,21 @@ class TestZone(TestCase): # NS for exactly the sub is allowed zone = Zone('unit.tests.', set(['sub', 'barred'])) - record = Record.new(zone, 'sub', { - 'ttl': 3600, - 'type': 'NS', - 'values': ['1.2.3.4.', '2.3.4.5.'], - }) + record = Record.new( + zone, + 'sub', + {'ttl': 3600, 'type': 'NS', 'values': ['1.2.3.4.', '2.3.4.5.']}, + ) zone.add_record(record) self.assertEqual(set([record]), zone.records) # non-NS for exactly the sub is rejected zone = Zone('unit.tests.', set(['sub', 'barred'])) - record = Record.new(zone, 'sub', { - 'ttl': 3600, - 'type': 'A', - 'values': ['1.2.3.4', '2.3.4.5'], - }) + record = Record.new( + zone, + 'sub', + {'ttl': 3600, 'type': 'A', 'values': ['1.2.3.4', '2.3.4.5']}, + ) with self.assertRaises(SubzoneRecordException) as ctx: zone.add_record(record) self.assertTrue('not of type NS', str(ctx.exception)) @@ -168,11 +182,11 @@ class TestZone(TestCase): # NS for something below the sub is rejected zone = Zone('unit.tests.', set(['sub', 'barred'])) - record = Record.new(zone, 'foo.sub', { - 'ttl': 3600, - 'type': 'NS', - 'values': ['1.2.3.4.', '2.3.4.5.'], - }) + record = Record.new( + zone, + 'foo.sub', + {'ttl': 3600, 'type': 'NS', 'values': ['1.2.3.4.', '2.3.4.5.']}, + ) with self.assertRaises(SubzoneRecordException) as ctx: zone.add_record(record) self.assertTrue('under a managed sub-zone', str(ctx.exception)) @@ -182,11 +196,11 @@ class TestZone(TestCase): # A for something below the sub is rejected zone = Zone('unit.tests.', set(['sub', 'barred'])) - record = Record.new(zone, 'foo.bar.sub', { - 'ttl': 3600, - 'type': 'A', - 'values': ['1.2.3.4', '2.3.4.5'], - }) + record = Record.new( + zone, + 'foo.bar.sub', + {'ttl': 3600, 'type': 'A', 'values': ['1.2.3.4', '2.3.4.5']}, + ) with self.assertRaises(SubzoneRecordException) as ctx: zone.add_record(record) self.assertTrue('under a managed sub-zone', str(ctx.exception)) @@ -199,21 +213,21 @@ class TestZone(TestCase): zone_ignored = Zone('unit.tests.', []) zone_missing = Zone('unit.tests.', []) - normal = Record.new(zone_normal, 'www', { - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + normal = Record.new( + zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'} + ) zone_normal.add_record(normal) - ignored = Record.new(zone_ignored, 'www', { - 'octodns': { - 'ignored': True + ignored = Record.new( + zone_ignored, + 'www', + { + 'octodns': {'ignored': True}, + 'ttl': 60, + 'type': 'A', + 'value': '9.9.9.9', }, - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + ) zone_ignored.add_record(ignored) provider = SimpleProvider() @@ -229,16 +243,12 @@ class TestZone(TestCase): def test_cname_coexisting(self): zone = Zone('unit.tests.', []) - a = Record.new(zone, 'www', { - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) - cname = Record.new(zone, 'www', { - 'ttl': 60, - 'type': 'CNAME', - 'value': 'foo.bar.com.', - }) + a = Record.new( + zone, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'} + ) + cname = Record.new( + zone, 'www', {'ttl': 60, 'type': 'CNAME', 'value': 'foo.bar.com.'} + ) # add cname to a zone.add_record(a) @@ -262,21 +272,21 @@ class TestZone(TestCase): zone_excluded = Zone('unit.tests.', []) zone_missing = Zone('unit.tests.', []) - normal = Record.new(zone_normal, 'www', { - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + normal = Record.new( + zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'} + ) zone_normal.add_record(normal) - excluded = Record.new(zone_excluded, 'www', { - 'octodns': { - 'excluded': ['test'] + excluded = Record.new( + zone_excluded, + 'www', + { + 'octodns': {'excluded': ['test']}, + 'ttl': 60, + 'type': 'A', + 'value': '9.9.9.9', }, - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + ) zone_excluded.add_record(excluded) provider = SimpleProvider() @@ -295,21 +305,21 @@ class TestZone(TestCase): zone_included = Zone('unit.tests.', []) zone_missing = Zone('unit.tests.', []) - normal = Record.new(zone_normal, 'www', { - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + normal = Record.new( + zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'} + ) zone_normal.add_record(normal) - included = Record.new(zone_included, 'www', { - 'octodns': { - 'included': ['test'] + included = Record.new( + zone_included, + 'www', + { + 'octodns': {'included': ['test']}, + 'ttl': 60, + 'type': 'A', + 'value': '9.9.9.9', }, - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + ) zone_included.add_record(included) provider = SimpleProvider() @@ -328,21 +338,21 @@ class TestZone(TestCase): zone_included = Zone('unit.tests.', []) zone_missing = Zone('unit.tests.', []) - normal = Record.new(zone_normal, 'www', { - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + normal = Record.new( + zone_normal, 'www', {'ttl': 60, 'type': 'A', 'value': '9.9.9.9'} + ) zone_normal.add_record(normal) - included = Record.new(zone_included, 'www', { - 'octodns': { - 'included': ['not-here'] + included = Record.new( + zone_included, + 'www', + { + 'octodns': {'included': ['not-here']}, + 'ttl': 60, + 'type': 'A', + 'value': '9.9.9.9', }, - 'ttl': 60, - 'type': 'A', - 'value': '9.9.9.9', - }) + ) zone_included.add_record(included) provider = SimpleProvider() @@ -420,18 +430,20 @@ class TestZone(TestCase): # No root NS yet self.assertFalse(zone.root_ns) - non_root_ns = NsRecord(zone, 'sub', {'ttl': 42, 'values': ( - 'ns1.unit.tests.', - 'ns2.unit.tests.', - )}) + non_root_ns = NsRecord( + zone, + 'sub', + {'ttl': 42, 'values': ('ns1.unit.tests.', 'ns2.unit.tests.')}, + ) zone.add_record(non_root_ns) # No root NS yet b/c this was a sub self.assertFalse(zone.root_ns) - root_ns = NsRecord(zone, '', {'ttl': 42, 'values': ( - 'ns3.unit.tests.', - 'ns4.unit.tests.', - )}) + root_ns = NsRecord( + zone, + '', + {'ttl': 42, 'values': ('ns3.unit.tests.', 'ns4.unit.tests.')}, + ) zone.add_record(root_ns) # Now we have a root NS self.assertEqual(root_ns, zone.root_ns)
OperationValue Source
Create') diff --git a/octodns/provider/powerdns.py b/octodns/provider/powerdns.py index 7a28f46..fd76abf 100644 --- a/octodns/provider/powerdns.py +++ b/octodns/provider/powerdns.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('PowerDns') try: - logger.warning('octodns_powerdns shimmed. Update your provider class to ' - 'octodns_powerdns.PowerDnsProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_powerdns shimmed. Update your provider class to ' + 'octodns_powerdns.PowerDnsProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_powerdns import PowerDnsProvider, PowerDnsBaseProvider + PowerDnsProvider # pragma: no cover PowerDnsBaseProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('PowerDnsProvider has been moved into a separate module, ' - 'octodns_powerdns is now required. Provider class should ' - 'be updated to octodns_powerdns.PowerDnsProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'PowerDnsProvider has been moved into a separate module, ' + 'octodns_powerdns is now required. Provider class should ' + 'be updated to octodns_powerdns.PowerDnsProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/rackspace.py b/octodns/provider/rackspace.py index 77436be..58b3443 100644 --- a/octodns/provider/rackspace.py +++ b/octodns/provider/rackspace.py @@ -2,23 +2,32 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Rackspace') try: - logger.warning('octodns_rackspace shimmed. Update your provider class to ' - 'octodns_rackspace.RackspaceProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_rackspace shimmed. Update your provider class to ' + 'octodns_rackspace.RackspaceProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_rackspace import RackspaceProvider + RackspaceProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('RackspaceProvider has been moved into a separate ' - 'module, octodns_rackspace is now required. Provider ' - 'class should be updated to ' - 'octodns_rackspace.RackspaceProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'RackspaceProvider has been moved into a separate ' + 'module, octodns_rackspace is now required. Provider ' + 'class should be updated to ' + 'octodns_rackspace.RackspaceProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/route53.py b/octodns/provider/route53.py index 744281e..f5937c0 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Route53') try: - logger.warning('octodns_route53 shimmed. Update your provider class to ' - 'octodns_route53.Route53Provider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_route53 shimmed. Update your provider class to ' + 'octodns_route53.Route53Provider. ' + 'Shim will be removed in 1.0' + ) from octodns_route53 import Route53Provider + Route53Provider # pragma: no cover except ModuleNotFoundError: - logger.exception('Route53Provider has been moved into a separate module, ' - 'octodns_route53 is now required. Provider class should ' - 'be updated to octodns_route53.Route53Provider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'Route53Provider has been moved into a separate module, ' + 'octodns_route53 is now required. Provider class should ' + 'be updated to octodns_route53.Route53Provider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/selectel.py b/octodns/provider/selectel.py index 44325db..04fcf7b 100644 --- a/octodns/provider/selectel.py +++ b/octodns/provider/selectel.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Selectel') try: - logger.warning('octodns_selectel shimmed. Update your provider class to ' - 'octodns_selectel.SelectelProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_selectel shimmed. Update your provider class to ' + 'octodns_selectel.SelectelProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_selectel import SelectelProvider + SelectelProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('SelectelProvider has been moved into a separate module, ' - 'octodns_selectel is now required. Provider class should ' - 'be updated to octodns_selectel.SelectelProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'SelectelProvider has been moved into a separate module, ' + 'octodns_selectel is now required. Provider class should ' + 'be updated to octodns_selectel.SelectelProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/transip.py b/octodns/provider/transip.py index 5282b42..c6c1e8c 100644 --- a/octodns/provider/transip.py +++ b/octodns/provider/transip.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Transip') try: - logger.warning('octodns_transip shimmed. Update your provider class to ' - 'octodns_transip.TransipProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_transip shimmed. Update your provider class to ' + 'octodns_transip.TransipProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_transip import TransipProvider + TransipProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('TransipProvider has been moved into a separate module, ' - 'octodns_transip is now required. Provider class should ' - 'be updated to octodns_transip.TransipProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'TransipProvider has been moved into a separate module, ' + 'octodns_transip is now required. Provider class should ' + 'be updated to octodns_transip.TransipProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/ultra.py b/octodns/provider/ultra.py index 6ccaf7a..271621b 100644 --- a/octodns/provider/ultra.py +++ b/octodns/provider/ultra.py @@ -2,22 +2,31 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from logging import getLogger logger = getLogger('Ultra') try: - logger.warning('octodns_ultra shimmed. Update your provider class to ' - 'octodns_ultra.UltraProvider. ' - 'Shim will be removed in 1.0') + logger.warning( + 'octodns_ultra shimmed. Update your provider class to ' + 'octodns_ultra.UltraProvider. ' + 'Shim will be removed in 1.0' + ) from octodns_ultra import UltraProvider + UltraProvider # pragma: no cover except ModuleNotFoundError: - logger.exception('UltraProvider has been moved into a separate module, ' - 'octodns_ultra is now required. Provider class should ' - 'be updated to octodns_ultra.UltraProvider. See ' - 'https://github.com/octodns/octodns#updating-' - 'to-use-extracted-providers for more information.') + logger.exception( + 'UltraProvider has been moved into a separate module, ' + 'octodns_ultra is now required. Provider class should ' + 'be updated to octodns_ultra.UltraProvider. See ' + 'https://github.com/octodns/octodns#updating-' + 'to-use-extracted-providers for more information.' + ) raise diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 16a4bc1..883f316 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from collections import defaultdict from os import listdir, makedirs @@ -102,23 +106,54 @@ class YamlProvider(BaseProvider): `--config-file=internal.yaml` ''' + SUPPORTS_GEO = True 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')) + SUPPORTS = set( + ( + 'A', + 'AAAA', + 'ALIAS', + 'CAA', + 'CNAME', + 'DNAME', + 'LOC', + 'MX', + 'NAPTR', + 'NS', + 'PTR', + 'SSHFP', + 'SPF', + 'SRV', + 'TXT', + 'URLFWD', + ) + ) - def __init__(self, id, directory, default_ttl=3600, enforce_order=True, - populate_should_replace=False, supports_root_ns=True, - *args, **kwargs): + def __init__( + self, + id, + directory, + default_ttl=3600, + enforce_order=True, + populate_should_replace=False, + supports_root_ns=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=%d', - id, directory, default_ttl, enforce_order, - populate_should_replace) + self.log.debug( + '__init__: id=%s, directory=%s, default_ttl=%d, ' + 'enforce_order=%d, populate_should_replace=%d', + id, + directory, + default_ttl, + enforce_order, + populate_should_replace, + ) super(YamlProvider, self).__init__(id, *args, **kwargs) self.directory = directory self.default_ttl = default_ttl @@ -140,16 +175,25 @@ class YamlProvider(BaseProvider): for d in data: if 'ttl' not in d: d['ttl'] = self.default_ttl - record = Record.new(zone, name, d, source=self, - lenient=lenient) - zone.add_record(record, lenient=lenient, - replace=self.populate_should_replace) - self.log.debug('_populate_from_file: successfully loaded "%s"', - filename) + record = Record.new( + zone, name, d, source=self, lenient=lenient + ) + zone.add_record( + record, + lenient=lenient, + replace=self.populate_should_replace, + ) + self.log.debug( + '_populate_from_file: successfully loaded "%s"', filename + ) def populate(self, zone, target=False, lenient=False): - self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, - target, lenient) + self.log.debug( + 'populate: name=%s, target=%s, lenient=%s', + zone.name, + target, + lenient, + ) if target: # When acting as a target we ignore any existing records so that we @@ -160,15 +204,18 @@ class YamlProvider(BaseProvider): filename = join(self.directory, f'{zone.name}yaml') self._populate_from_file(filename, zone, lenient) - self.log.info('populate: found %s records, exists=False', - len(zone.records) - before) + self.log.info( + 'populate: found %s records, exists=False', + len(zone.records) - before, + ) return False def _apply(self, plan): desired = plan.desired changes = plan.changes - self.log.debug('_apply: zone=%s, len(changes)=%d', desired.name, - len(changes)) + self.log.debug( + '_apply: zone=%s, len(changes)=%d', desired.name, len(changes) + ) # Since we don't have existing we'll only see creates records = [c.new for c in changes] # Order things alphabetically (records sort that way @@ -257,8 +304,12 @@ class SplitYamlProvider(YamlProvider): return join(self.directory, filename) def populate(self, zone, target=False, lenient=False): - self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, - target, lenient) + self.log.debug( + 'populate: name=%s, target=%s, lenient=%s', + zone.name, + target, + lenient, + ) if target: # When acting as a target we ignore any existing records so that we @@ -271,8 +322,10 @@ class SplitYamlProvider(YamlProvider): for yaml_filename in yaml_filenames: self._populate_from_file(yaml_filename, zone, lenient) - self.log.info('populate: found %s records, exists=False', - len(zone.records) - before) + self.log.info( + 'populate: found %s records, exists=False', + len(zone.records) - before, + ) return False def _do_apply(self, desired, data): diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index ddb8b92..871486e 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from ipaddress import IPv4Address, IPv6Address from logging import getLogger @@ -16,7 +20,6 @@ from .geo import GeoCodes class Change(EqualityTupleMixin): - def __init__(self, existing, new): self.existing = existing self.new = new @@ -50,8 +53,10 @@ class Update(Change): # do nothing def __repr__(self, leader=''): source = self.new.source.id if self.new.source else '' - return f'Update\n{leader} {self.existing} ->\n' \ + return ( + f'Update\n{leader} {self.existing} ->\n' f'{leader} {self.new} ({source})' + ) class Delete(Change): @@ -69,7 +74,6 @@ class RecordException(Exception): class ValidationError(RecordException): - @classmethod def build_message(cls, fqdn, reasons): reasons = '\n - '.join(reasons) @@ -129,13 +133,17 @@ class Record(EqualityTupleMixin): reasons.append('invalid name "@", use "" instead') n = len(fqdn) if n > 253: - reasons.append(f'invalid fqdn, "{fqdn}" is too long at {n} ' - 'chars, max is 253') + reasons.append( + f'invalid fqdn, "{fqdn}" is too long at {n} ' + 'chars, max is 253' + ) for label in name.split('.'): n = len(label) if n > 63: - reasons.append(f'invalid label, "{label}" is too long at {n}' - ' chars, max is 63') + reasons.append( + f'invalid label, "{label}" is too long at {n}' + ' chars, max is 63' + ) try: ttl = int(data['ttl']) if ttl < 0: @@ -143,16 +151,23 @@ class Record(EqualityTupleMixin): except KeyError: reasons.append('missing ttl') try: - if data['octodns']['healthcheck']['protocol'] \ - not in ('HTTP', 'HTTPS', 'TCP'): + if data['octodns']['healthcheck']['protocol'] not in ( + 'HTTP', + 'HTTPS', + 'TCP', + ): reasons.append('invalid healthcheck protocol') except KeyError: pass return reasons def __init__(self, zone, name, data, source=None): - self.log.debug('__init__: zone.name=%s, type=%11s, name=%s', zone.name, - self.__class__.__name__, name) + self.log.debug( + '__init__: zone.name=%s, type=%11s, name=%s', + zone.name, + self.__class__.__name__, + name, + ) self.zone = zone # force everything lower-case just to be safe self.name = str(name).lower() if name else name @@ -231,7 +246,7 @@ class Record(EqualityTupleMixin): self.name, data, self.source, - lenient=True + lenient=True, ) # NOTE: we're using __hash__ and ordering methods that consider Records @@ -250,8 +265,10 @@ class Record(EqualityTupleMixin): class GeoValue(EqualityTupleMixin): - geo_re = re.compile(r'^(?P\w\w)(-(?P\w\w)' - r'(-(?P\w\w))?)?$') + geo_re = re.compile( + r'^(?P\w\w)(-(?P\w\w)' + r'(-(?P\w\w))?)?$' + ) @classmethod def _validate_geo(cls, code): @@ -277,16 +294,21 @@ class GeoValue(EqualityTupleMixin): bits.pop() def _equality_tuple(self): - return (self.continent_code, self.country_code, self.subdivision_code, - self.values) + return ( + self.continent_code, + self.country_code, + self.subdivision_code, + self.values, + ) def __repr__(self): - return f"'Geo {self.continent_code} {self.country_code} " \ + return ( + f"'Geo {self.continent_code} {self.country_code} " "{self.subdivision_code} {self.values}'" + ) class ValuesMixin(object): - @classmethod def validate(cls, name, fqdn, data): reasons = super(ValuesMixin, cls).validate(name, fqdn, data) @@ -377,18 +399,20 @@ class _GeoMixin(ValuesMixin): def __repr__(self): if self.geo: klass = self.__class__.__name__ - return f'<{klass} {self._type} {self.ttl}, {self.fqdn}, ' \ + return ( + f'<{klass} {self._type} {self.ttl}, {self.fqdn}, ' f'{self.values}, {self.geo}>' + ) return super(_GeoMixin, self).__repr__() class ValueMixin(object): - @classmethod def validate(cls, name, fqdn, data): reasons = super(ValueMixin, cls).validate(name, fqdn, data) - reasons.extend(cls._value_type.validate(data.get('value', None), - cls._type)) + reasons.extend( + cls._value_type.validate(data.get('value', None), cls._type) + ) return reasons def __init__(self, zone, name, data, source=None): @@ -422,7 +446,8 @@ class _DynamicPool(object): 'value': d['value'], 'weight': d.get('weight', 1), 'status': d.get('status', 'obey'), - } for d in data['values'] + } + for d in data['values'] ] values.sort(key=lambda d: d['value']) @@ -432,7 +457,9 @@ class _DynamicPool(object): if weight != 1: self.log.warning( 'Using weight=1 instead of %s for single-value pool %s', - weight, _id) + weight, + _id, + ) values[0]['weight'] = 1 fallback = data.get('fallback', None) @@ -457,7 +484,6 @@ class _DynamicPool(object): class _DynamicRule(object): - def __init__(self, i, data): self.i = i @@ -487,7 +513,6 @@ class _DynamicRule(object): class _Dynamic(object): - def __init__(self, pools, rules): self.pools = pools self.rules = rules @@ -499,10 +524,7 @@ class _Dynamic(object): rules = [] for rule in self.rules: rules.append(rule._data()) - return { - 'pools': pools, - 'rules': rules, - } + return {'pools': pools, 'rules': rules} def __eq__(self, other): if not isinstance(other, _Dynamic): @@ -518,8 +540,10 @@ class _Dynamic(object): class _DynamicMixin(object): - geo_re = re.compile(r'^(?P\w\w)(-(?P\w\w)' - r'(-(?P\w\w))?)?$') + geo_re = re.compile( + r'^(?P\w\w)(-(?P\w\w)' + r'(-(?P\w\w))?)?$' + ) @classmethod def validate(cls, name, fqdn, data): @@ -561,41 +585,53 @@ class _DynamicMixin(object): weight = value['weight'] weight = int(weight) if weight < 1 or weight > 100: - reasons.append(f'invalid weight "{weight}" in ' - f'pool "{_id}" value {value_num}') + reasons.append( + f'invalid weight "{weight}" in ' + f'pool "{_id}" value {value_num}' + ) except KeyError: pass except ValueError: - reasons.append(f'invalid weight "{weight}" in ' - f'pool "{_id}" value {value_num}') + reasons.append( + f'invalid weight "{weight}" in ' + f'pool "{_id}" value {value_num}' + ) try: status = value['status'] if status not in ['up', 'down', 'obey']: - reasons.append(f'invalid status "{status}" in ' - f'pool "{_id}" value {value_num}') + reasons.append( + f'invalid status "{status}" in ' + f'pool "{_id}" value {value_num}' + ) except KeyError: pass try: value = value['value'] - reasons.extend(cls._value_type.validate(value, - cls._type)) + reasons.extend( + cls._value_type.validate(value, cls._type) + ) except KeyError: - reasons.append(f'missing value in pool "{_id}" ' - f'value {value_num}') + reasons.append( + f'missing value in pool "{_id}" ' + f'value {value_num}' + ) if len(values) == 1 and values[0].get('weight', 1) != 1: - reasons.append(f'pool "{_id}" has single value with ' - 'weight!=1') + reasons.append( + f'pool "{_id}" has single value with ' 'weight!=1' + ) fallback = pool.get('fallback', None) if fallback is not None: if fallback in pools: pools_seen_as_fallback.add(fallback) else: - reasons.append(f'undefined fallback "{fallback}" ' - f'for pool "{_id}"') + reasons.append( + f'undefined fallback "{fallback}" ' + f'for pool "{_id}"' + ) # Check for loops fallback = pools[_id].get('fallback', None) @@ -639,11 +675,14 @@ class _DynamicMixin(object): reasons.append(f'rule {rule_num} invalid pool "{pool}"') else: if pool not in pools: - reasons.append(f'rule {rule_num} undefined pool ' - f'"{pool}"') + reasons.append( + f'rule {rule_num} undefined pool ' f'"{pool}"' + ) elif pool in pools_seen and geos: - reasons.append(f'rule {rule_num} invalid, target ' - f'pool "{pool}" reused') + reasons.append( + f'rule {rule_num} invalid, target ' + f'pool "{pool}" reused' + ) pools_seen.add(pool) if not geos: @@ -655,8 +694,9 @@ class _DynamicMixin(object): reasons.append(f'rule {rule_num} geos must be a list') else: for geo in geos: - reasons.extend(GeoCodes.validate(geo, - f'rule {rule_num} ')) + reasons.extend( + GeoCodes.validate(geo, f'rule {rule_num} ') + ) unused = pools_exist - pools_seen - pools_seen_as_fallback if unused: @@ -666,8 +706,7 @@ class _DynamicMixin(object): return reasons def __init__(self, zone, name, data, *args, **kwargs): - super(_DynamicMixin, self).__init__(zone, name, data, *args, - **kwargs) + super(_DynamicMixin, self).__init__(zone, name, data, *args, **kwargs) self.dynamic = {} @@ -720,13 +759,14 @@ class _DynamicMixin(object): values = self.value klass = self.__class__.__name__ - return f'<{klass} {self._type} {self.ttl}, {self.fqdn}, ' \ + return ( + f'<{klass} {self._type} {self.ttl}, {self.fqdn}, ' f'{values}, {self.dynamic}>' + ) return super(_DynamicMixin, self).__repr__() class _IpList(object): - @classmethod def validate(cls, data, _type): if not isinstance(data, (list, tuple)): @@ -754,8 +794,7 @@ class _IpList(object): values = [str(v) if v is not None else '' for v in values] # Now round trip all non-'' through the address type and back to a str # to normalize the address representation. - return [str(cls._address_type(v)) if v != '' else '' - for v in values] + return [str(cls._address_type(v)) if v != '' else '' for v in values] class Ipv4List(_IpList): @@ -769,7 +808,6 @@ class Ipv6List(_IpList): class _TargetValue(object): - @classmethod def validate(cls, data, _type): reasons = [] @@ -869,11 +907,7 @@ class CaaValue(EqualityTupleMixin): @property def data(self): - return { - 'flags': self.flags, - 'tag': self.tag, - 'value': self.value, - } + return {'flags': self.flags, 'tag': self.tag, 'value': self.value} def _equality_tuple(self): return (self.flags, self.tag, self.value) @@ -935,10 +969,7 @@ class LocValue(EqualityTupleMixin): 'precision_vert', ] - direction_keys = [ - 'lat_direction', - 'long_direction', - ] + direction_keys = ['lat_direction', 'long_direction'] if not isinstance(data, (list, tuple)): data = (data,) @@ -949,18 +980,21 @@ class LocValue(EqualityTupleMixin): int(value[key]) if ( ( - key == 'lat_degrees' and - not 0 <= int(value[key]) <= 90 - ) or ( - key == 'long_degrees' and - not 0 <= int(value[key]) <= 180 - ) or ( - key in ['lat_minutes', 'long_minutes'] and - not 0 <= int(value[key]) <= 59 + key == 'lat_degrees' + and not 0 <= int(value[key]) <= 90 + ) + or ( + key == 'long_degrees' + and not 0 <= int(value[key]) <= 180 + ) + or ( + key in ['lat_minutes', 'long_minutes'] + and not 0 <= int(value[key]) <= 59 ) ): - reasons.append(f'invalid value for {key} ' - f'"{value[key]}"') + reasons.append( + f'invalid value for {key} ' f'"{value[key]}"' + ) except KeyError: reasons.append(f'missing {key}') except ValueError: @@ -971,20 +1005,23 @@ class LocValue(EqualityTupleMixin): float(value[key]) if ( ( - key in ['lat_seconds', 'long_seconds'] and - not 0 <= float(value[key]) <= 59.999 - ) or ( - key == 'altitude' and - not -100000.00 <= float(value[key]) <= 42849672.95 - ) or ( - key in ['size', - 'precision_horz', - 'precision_vert'] and - not 0 <= float(value[key]) <= 90000000.00 + key in ['lat_seconds', 'long_seconds'] + and not 0 <= float(value[key]) <= 59.999 + ) + or ( + key == 'altitude' + and not -100000.00 + <= float(value[key]) + <= 42849672.95 + ) + or ( + key in ['size', 'precision_horz', 'precision_vert'] + and not 0 <= float(value[key]) <= 90000000.00 ) ): - reasons.append(f'invalid value for {key} ' - f'"{value[key]}"') + reasons.append( + f'invalid value for {key} ' f'"{value[key]}"' + ) except KeyError: reasons.append(f'missing {key}') except ValueError: @@ -993,18 +1030,14 @@ class LocValue(EqualityTupleMixin): for key in direction_keys: try: str(value[key]) - if ( - key == 'lat_direction' and - value[key] not in ['N', 'S'] - ): - reasons.append(f'invalid direction for {key} ' - f'"{value[key]}"') - if ( - key == 'long_direction' and - value[key] not in ['E', 'W'] - ): - reasons.append(f'invalid direction for {key} ' - f'"{value[key]}"') + if key == 'lat_direction' and value[key] not in ['N', 'S']: + reasons.append( + f'invalid direction for {key} ' f'"{value[key]}"' + ) + if key == 'long_direction' and value[key] not in ['E', 'W']: + reasons.append( + f'invalid direction for {key} ' f'"{value[key]}"' + ) except KeyError: reasons.append(f'missing {key}') return reasons @@ -1045,20 +1078,22 @@ class LocValue(EqualityTupleMixin): } def __hash__(self): - return hash(( - self.lat_degrees, - self.lat_minutes, - self.lat_seconds, - self.lat_direction, - self.long_degrees, - self.long_minutes, - self.long_seconds, - self.long_direction, - self.altitude, - self.size, - self.precision_horz, - self.precision_vert, - )) + return hash( + ( + self.lat_degrees, + self.lat_minutes, + self.lat_seconds, + self.lat_direction, + self.long_degrees, + self.long_minutes, + self.long_seconds, + self.long_direction, + self.altitude, + self.size, + self.precision_horz, + self.precision_vert, + ) + ) def _equality_tuple(self): return ( @@ -1077,12 +1112,14 @@ class LocValue(EqualityTupleMixin): ) def __repr__(self): - return f"'{self.lat_degrees} {self.lat_minutes} " \ - f"{self.lat_seconds:.3f} {self.lat_direction} " \ - f"{self.long_degrees} {self.long_minutes} " \ - f"{self.long_seconds:.3f} {self.long_direction} " \ - f"{self.altitude:.2f}m {self.size:.2f}m " \ + return ( + f"'{self.lat_degrees} {self.lat_minutes} " + f"{self.lat_seconds:.3f} {self.lat_direction} " + f"{self.long_degrees} {self.long_minutes} " + f"{self.long_seconds:.3f} {self.long_direction} " + f"{self.altitude:.2f}m {self.size:.2f}m " f"{self.precision_horz:.2f}m {self.precision_vert:.2f}m'" + ) class LocRecord(ValuesMixin, Record): @@ -1094,7 +1131,6 @@ Record.register_type(LocRecord) class MxValue(EqualityTupleMixin): - @classmethod def validate(cls, data, _type): if not isinstance(data, (list, tuple)): @@ -1113,10 +1149,14 @@ class MxValue(EqualityTupleMixin): exchange = None try: exchange = str(value.get('exchange', None) or value['value']) - if exchange != '.' and \ - not FQDN(exchange, allow_underscores=True).is_valid: - reasons.append(f'Invalid MX exchange "{exchange}" is not ' - 'a valid FQDN.') + if ( + exchange != '.' + and not FQDN(exchange, allow_underscores=True).is_valid + ): + reasons.append( + f'Invalid MX exchange "{exchange}" is not ' + 'a valid FQDN.' + ) elif not exchange.endswith('.'): reasons.append(f'MX value "{exchange}" missing trailing .') except KeyError: @@ -1143,10 +1183,7 @@ class MxValue(EqualityTupleMixin): @property def data(self): - return { - 'preference': self.preference, - 'exchange': self.exchange, - } + return {'preference': self.preference, 'exchange': self.exchange} def __hash__(self): return hash((self.preference, self.exchange)) @@ -1228,15 +1265,23 @@ class NaptrValue(EqualityTupleMixin): return hash(self.__repr__()) def _equality_tuple(self): - return (self.order, self.preference, self.flags, self.service, - self.regexp, self.replacement) + return ( + self.order, + self.preference, + self.flags, + self.service, + self.regexp, + self.replacement, + ) def __repr__(self): flags = self.flags if self.flags is not None else '' service = self.service if self.service is not None else '' regexp = self.regexp if self.regexp is not None else '' - return f"'{self.order} {self.preference} \"{flags}\" \"{service}\" " \ + return ( + f"'{self.order} {self.preference} \"{flags}\" \"{service}\" " f"\"{regexp}\" {self.replacement}'" + ) class NaptrRecord(ValuesMixin, Record): @@ -1248,7 +1293,6 @@ Record.register_type(NaptrRecord) class _NsValue(object): - @classmethod def validate(cls, data, _type): if not data: @@ -1258,8 +1302,9 @@ class _NsValue(object): reasons = [] 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.') + reasons.append( + f'Invalid NS value "{value}" is not ' 'a valid FQDN.' + ) elif not value.endswith('.'): reasons.append(f'NS value "{value}" missing trailing .') return reasons @@ -1278,7 +1323,6 @@ Record.register_type(NsRecord) class PtrValue(_TargetValue): - @classmethod def validate(cls, values, _type): if not isinstance(values, list): @@ -1334,13 +1378,15 @@ class SshfpValue(EqualityTupleMixin): try: fingerprint_type = int(value['fingerprint_type']) if fingerprint_type not in cls.VALID_FINGERPRINT_TYPES: - reasons.append('unrecognized fingerprint_type ' - f'"{fingerprint_type}"') + reasons.append( + 'unrecognized fingerprint_type ' f'"{fingerprint_type}"' + ) except KeyError: reasons.append('missing fingerprint_type') except ValueError: - reasons.append('invalid fingerprint_type ' - f'"{value["fingerprint_type"]}"') + reasons.append( + 'invalid fingerprint_type ' f'"{value["fingerprint_type"]}"' + ) if 'fingerprint' not in value: reasons.append('missing fingerprint') return reasons @@ -1386,8 +1432,10 @@ class _ChunkedValuesMixin(ValuesMixin): def chunked_value(self, value): value = value.replace('"', '\\"') - vs = [value[i:i + self.CHUNK_SIZE] - for i in range(0, len(value), self.CHUNK_SIZE)] + vs = [ + value[i : i + self.CHUNK_SIZE] + for i in range(0, len(value), self.CHUNK_SIZE) + ] vs = '" "'.join(vs) return f'"{vs}"' @@ -1433,7 +1481,6 @@ Record.register_type(SpfRecord) class SrvValue(EqualityTupleMixin): - @classmethod def validate(cls, data, _type): if not isinstance(data, (list, tuple)): @@ -1463,10 +1510,13 @@ class SrvValue(EqualityTupleMixin): target = value['target'] if not target.endswith('.'): reasons.append(f'SRV value "{target}" missing trailing .') - if target != '.' and \ - not FQDN(str(target), allow_underscores=True).is_valid: - reasons.append(f'Invalid SRV target "{target}" is not ' - 'a valid FQDN.') + if ( + target != '.' + and not FQDN(str(target), allow_underscores=True).is_valid + ): + reasons.append( + f'Invalid SRV target "{target}" is not ' 'a valid FQDN.' + ) except KeyError: reasons.append('missing target') return reasons @@ -1518,7 +1568,6 @@ Record.register_type(SrvRecord) class TlsaValue(EqualityTupleMixin): - @classmethod def validate(cls, data, _type): if not isinstance(data, (list, tuple)): @@ -1528,11 +1577,14 @@ class TlsaValue(EqualityTupleMixin): try: certificate_usage = int(value.get('certificate_usage', 0)) if certificate_usage < 0 or certificate_usage > 3: - reasons.append(f'invalid certificate_usage ' - f'"{certificate_usage}"') + reasons.append( + f'invalid certificate_usage ' f'"{certificate_usage}"' + ) except ValueError: - reasons.append(f'invalid certificate_usage ' - f'"{value["certificate_usage"]}"') + reasons.append( + f'invalid certificate_usage ' + f'"{value["certificate_usage"]}"' + ) try: selector = int(value.get('selector', 0)) @@ -1546,8 +1598,9 @@ class TlsaValue(EqualityTupleMixin): if matching_type < 0 or matching_type > 2: reasons.append(f'invalid matching_type "{matching_type}"') except ValueError: - reasons.append(f'invalid matching_type ' - f'"{value["matching_type"]}"') + reasons.append( + f'invalid matching_type ' f'"{value["matching_type"]}"' + ) if 'certificate_usage' not in value: reasons.append('missing certificate_usage') @@ -1567,8 +1620,9 @@ class TlsaValue(EqualityTupleMixin): self.certificate_usage = int(value.get('certificate_usage', 0)) self.selector = int(value.get('selector', 0)) self.matching_type = int(value.get('matching_type', 0)) - self.certificate_association_data = \ - value['certificate_association_data'] + self.certificate_association_data = value[ + 'certificate_association_data' + ] @property def data(self): @@ -1580,12 +1634,18 @@ class TlsaValue(EqualityTupleMixin): } def _equality_tuple(self): - return (self.certificate_usage, self.selector, self.matching_type, - self.certificate_association_data) + return ( + self.certificate_usage, + self.selector, + self.matching_type, + self.certificate_association_data, + ) def __repr__(self): - return f"'{self.certificate_usage} {self.selector} '" \ - f"'{self.matching_type} {self.certificate_association_data}'" + return ( + f"'{self.certificate_usage} {self.selector} '" + f"'{self.matching_type} {self.certificate_association_data}'" + ) class TlsaRecord(ValuesMixin, Record): @@ -1676,8 +1736,10 @@ class UrlfwdValue(EqualityTupleMixin): return (self.path, self.target, self.code, self.masking, self.query) def __repr__(self): - return f'"{self.path}" "{self.target}" {self.code} ' \ + return ( + f'"{self.path}" "{self.target}" {self.code} ' f'{self.masking} {self.query}' + ) class UrlfwdRecord(ValuesMixin, Record): diff --git a/octodns/record/geo.py b/octodns/record/geo.py index cec8f25..73bffe2 100644 --- a/octodns/record/geo.py +++ b/octodns/record/geo.py @@ -29,8 +29,10 @@ class GeoCodes(object): reasons.append(f'{prefix}unknown continent code "{code}"') elif n > 1 and pieces[1] not in geo_data[pieces[0]]: reasons.append(f'{prefix}unknown country code "{code}"') - elif n > 2 and \ - pieces[2] not in geo_data[pieces[0]][pieces[1]]['provinces']: + elif ( + n > 2 + and pieces[2] not in geo_data[pieces[0]][pieces[1]]['provinces'] + ): reasons.append(f'{prefix}unknown province code "{code}"') return reasons @@ -64,10 +66,13 @@ class GeoCodes(object): def province_to_code(cls, province): # We cheat on this one a little since we only support provinces in # NA-US, NA-CA - if (province not in geo_data['NA']['US']['provinces'] and - province not in geo_data['NA']['CA']['provinces']): - cls.log.warning('country_to_code: unrecognized province "%s"', - province) + if ( + province not in geo_data['NA']['US']['provinces'] + and province not in geo_data['NA']['CA']['provinces'] + ): + cls.log.warning( + 'country_to_code: unrecognized province "%s"', province + ) return if province in geo_data['NA']['US']['provinces']: country = 'US' diff --git a/octodns/record/geo_data.py b/octodns/record/geo_data.py index 39fa5db..baee616 100644 --- a/octodns/record/geo_data.py +++ b/octodns/record/geo_data.py @@ -6,324 +6,346 @@ # do not modify it directly # -geo_data = \ - {'AF': {'AO': {'name': 'Angola'}, - 'BF': {'name': 'Burkina Faso'}, - 'BI': {'name': 'Burundi'}, - 'BJ': {'name': 'Benin'}, - 'BW': {'name': 'Botswana'}, - 'CD': {'name': 'Congo, The Democratic Republic of the'}, - 'CF': {'name': 'Central African Republic'}, - 'CG': {'name': 'Congo'}, - 'CI': {'name': "Côte d'Ivoire"}, - 'CM': {'name': 'Cameroon'}, - 'CV': {'name': 'Cabo Verde'}, - 'DJ': {'name': 'Djibouti'}, - 'DZ': {'name': 'Algeria'}, - 'EG': {'name': 'Egypt'}, - 'EH': {'name': 'Western Sahara'}, - 'ER': {'name': 'Eritrea'}, - 'ET': {'name': 'Ethiopia'}, - 'GA': {'name': 'Gabon'}, - 'GH': {'name': 'Ghana'}, - 'GM': {'name': 'Gambia'}, - 'GN': {'name': 'Guinea'}, - 'GQ': {'name': 'Equatorial Guinea'}, - 'GW': {'name': 'Guinea-Bissau'}, - 'KE': {'name': 'Kenya'}, - 'KM': {'name': 'Comoros'}, - 'LR': {'name': 'Liberia'}, - 'LS': {'name': 'Lesotho'}, - 'LY': {'name': 'Libya'}, - 'MA': {'name': 'Morocco'}, - 'MG': {'name': 'Madagascar'}, - 'ML': {'name': 'Mali'}, - 'MR': {'name': 'Mauritania'}, - 'MU': {'name': 'Mauritius'}, - 'MW': {'name': 'Malawi'}, - 'MZ': {'name': 'Mozambique'}, - 'NA': {'name': 'Namibia'}, - 'NE': {'name': 'Niger'}, - 'NG': {'name': 'Nigeria'}, - 'RE': {'name': 'Réunion'}, - 'RW': {'name': 'Rwanda'}, - 'SC': {'name': 'Seychelles'}, - 'SD': {'name': 'Sudan'}, - 'SH': {'name': 'Saint Helena, Ascension and Tristan da Cunha'}, - 'SL': {'name': 'Sierra Leone'}, - 'SN': {'name': 'Senegal'}, - 'SO': {'name': 'Somalia'}, - 'SS': {'name': 'South Sudan'}, - 'ST': {'name': 'Sao Tome and Principe'}, - 'SZ': {'name': 'Eswatini'}, - 'TD': {'name': 'Chad'}, - 'TG': {'name': 'Togo'}, - 'TN': {'name': 'Tunisia'}, - 'TZ': {'name': 'Tanzania, United Republic of'}, - 'UG': {'name': 'Uganda'}, - 'YT': {'name': 'Mayotte'}, - 'ZA': {'name': 'South Africa'}, - 'ZM': {'name': 'Zambia'}, - 'ZW': {'name': 'Zimbabwe'}}, - 'AN': {'AQ': {'name': 'Antarctica'}, - 'BV': {'name': 'Bouvet Island'}, - 'HM': {'name': 'Heard Island and McDonald Islands'}, - 'TF': {'name': 'French Southern Territories'}}, - 'AS': {'AE': {'name': 'United Arab Emirates'}, - 'AF': {'name': 'Afghanistan'}, - 'AM': {'name': 'Armenia'}, - 'AZ': {'name': 'Azerbaijan'}, - 'BD': {'name': 'Bangladesh'}, - 'BH': {'name': 'Bahrain'}, - 'BN': {'name': 'Brunei Darussalam'}, - 'BT': {'name': 'Bhutan'}, - 'CC': {'name': 'Cocos (Keeling) Islands'}, - 'CN': {'name': 'China'}, - 'CX': {'name': 'Christmas Island'}, - 'CY': {'name': 'Cyprus'}, - 'GE': {'name': 'Georgia'}, - 'HK': {'name': 'Hong Kong'}, - 'ID': {'name': 'Indonesia'}, - 'IL': {'name': 'Israel'}, - 'IN': {'name': 'India'}, - 'IO': {'name': 'British Indian Ocean Territory'}, - 'IQ': {'name': 'Iraq'}, - 'IR': {'name': 'Iran, Islamic Republic of'}, - 'JO': {'name': 'Jordan'}, - 'JP': {'name': 'Japan'}, - 'KG': {'name': 'Kyrgyzstan'}, - 'KH': {'name': 'Cambodia'}, - 'KP': {'name': "Korea, Democratic People's Republic of"}, - 'KR': {'name': 'Korea, Republic of'}, - 'KW': {'name': 'Kuwait'}, - 'KZ': {'name': 'Kazakhstan'}, - 'LA': {'name': "Lao People's Democratic Republic"}, - 'LB': {'name': 'Lebanon'}, - 'LK': {'name': 'Sri Lanka'}, - 'MM': {'name': 'Myanmar'}, - 'MN': {'name': 'Mongolia'}, - 'MO': {'name': 'Macao'}, - 'MV': {'name': 'Maldives'}, - 'MY': {'name': 'Malaysia'}, - 'NP': {'name': 'Nepal'}, - 'OM': {'name': 'Oman'}, - 'PH': {'name': 'Philippines'}, - 'PK': {'name': 'Pakistan'}, - 'PS': {'name': 'Palestine, State of'}, - 'QA': {'name': 'Qatar'}, - 'SA': {'name': 'Saudi Arabia'}, - 'SG': {'name': 'Singapore'}, - 'SY': {'name': 'Syrian Arab Republic'}, - 'TH': {'name': 'Thailand'}, - 'TJ': {'name': 'Tajikistan'}, - 'TL': {'name': 'Timor-Leste'}, - 'TM': {'name': 'Turkmenistan'}, - 'TR': {'name': 'Turkey'}, - 'TW': {'name': 'Taiwan, Province of China'}, - 'UZ': {'name': 'Uzbekistan'}, - 'VN': {'name': 'Viet Nam'}, - 'YE': {'name': 'Yemen'}}, - 'EU': {'AD': {'name': 'Andorra'}, - 'AL': {'name': 'Albania'}, - 'AT': {'name': 'Austria'}, - 'AX': {'name': 'Åland Islands'}, - 'BA': {'name': 'Bosnia and Herzegovina'}, - 'BE': {'name': 'Belgium'}, - 'BG': {'name': 'Bulgaria'}, - 'BY': {'name': 'Belarus'}, - 'CH': {'name': 'Switzerland'}, - 'CZ': {'name': 'Czechia'}, - 'DE': {'name': 'Germany'}, - 'DK': {'name': 'Denmark'}, - 'EE': {'name': 'Estonia'}, - 'ES': {'name': 'Spain'}, - 'FI': {'name': 'Finland'}, - 'FO': {'name': 'Faroe Islands'}, - 'FR': {'name': 'France'}, - 'GB': {'name': 'United Kingdom'}, - 'GG': {'name': 'Guernsey'}, - 'GI': {'name': 'Gibraltar'}, - 'GR': {'name': 'Greece'}, - 'HR': {'name': 'Croatia'}, - 'HU': {'name': 'Hungary'}, - 'IE': {'name': 'Ireland'}, - 'IM': {'name': 'Isle of Man'}, - 'IS': {'name': 'Iceland'}, - 'IT': {'name': 'Italy'}, - 'JE': {'name': 'Jersey'}, - 'LI': {'name': 'Liechtenstein'}, - 'LT': {'name': 'Lithuania'}, - 'LU': {'name': 'Luxembourg'}, - 'LV': {'name': 'Latvia'}, - 'MC': {'name': 'Monaco'}, - 'MD': {'name': 'Moldova, Republic of'}, - 'ME': {'name': 'Montenegro'}, - 'MK': {'name': 'North Macedonia'}, - 'MT': {'name': 'Malta'}, - 'NL': {'name': 'Netherlands'}, - 'NO': {'name': 'Norway'}, - 'PL': {'name': 'Poland'}, - 'PT': {'name': 'Portugal'}, - 'RO': {'name': 'Romania'}, - 'RS': {'name': 'Serbia'}, - 'RU': {'name': 'Russian Federation'}, - 'SE': {'name': 'Sweden'}, - 'SI': {'name': 'Slovenia'}, - 'SJ': {'name': 'Svalbard and Jan Mayen'}, - 'SK': {'name': 'Slovakia'}, - 'SM': {'name': 'San Marino'}, - 'UA': {'name': 'Ukraine'}, - 'VA': {'name': 'Holy See (Vatican City State)'}}, - 'NA': {'AG': {'name': 'Antigua and Barbuda'}, - 'AI': {'name': 'Anguilla'}, - 'AW': {'name': 'Aruba'}, - 'BB': {'name': 'Barbados'}, - 'BL': {'name': 'Saint Barthélemy'}, - 'BM': {'name': 'Bermuda'}, - 'BQ': {'name': 'Bonaire, Sint Eustatius and Saba'}, - 'BS': {'name': 'Bahamas'}, - 'BZ': {'name': 'Belize'}, - 'CA': {'name': 'Canada', - 'provinces': {'AB': {'name': 'Alberta'}, - 'BC': {'name': 'British Columbia'}, - 'MB': {'name': 'Manitoba'}, - 'NB': {'name': 'New Brunswick'}, - 'NL': {'name': 'Newfoundland and Labrador'}, - 'NS': {'name': 'Nova Scotia'}, - 'NT': {'name': 'Northwest Territories'}, - 'NU': {'name': 'Nunavut'}, - 'ON': {'name': 'Ontario'}, - 'PE': {'name': 'Prince Edward Island'}, - 'QC': {'name': 'Quebec'}, - 'SK': {'name': 'Saskatchewan'}, - 'YT': {'name': 'Yukon Territory'}}}, - 'CR': {'name': 'Costa Rica'}, - 'CU': {'name': 'Cuba'}, - 'CW': {'name': 'Curaçao'}, - 'DM': {'name': 'Dominica'}, - 'DO': {'name': 'Dominican Republic'}, - 'GD': {'name': 'Grenada'}, - 'GL': {'name': 'Greenland'}, - 'GP': {'name': 'Guadeloupe'}, - 'GT': {'name': 'Guatemala'}, - 'HN': {'name': 'Honduras'}, - 'HT': {'name': 'Haiti'}, - 'JM': {'name': 'Jamaica'}, - 'KN': {'name': 'Saint Kitts and Nevis'}, - 'KY': {'name': 'Cayman Islands'}, - 'LC': {'name': 'Saint Lucia'}, - 'MF': {'name': 'Saint Martin (French part)'}, - 'MQ': {'name': 'Martinique'}, - 'MS': {'name': 'Montserrat'}, - 'MX': {'name': 'Mexico'}, - 'NI': {'name': 'Nicaragua'}, - 'PA': {'name': 'Panama'}, - 'PM': {'name': 'Saint Pierre and Miquelon'}, - 'PR': {'name': 'Puerto Rico'}, - 'SV': {'name': 'El Salvador'}, - 'SX': {'name': 'Sint Maarten (Dutch part)'}, - 'TC': {'name': 'Turks and Caicos Islands'}, - 'TT': {'name': 'Trinidad and Tobago'}, - 'US': {'name': 'United States', - 'provinces': {'AK': {'name': 'Alaska'}, - 'AL': {'name': 'Alabama'}, - 'AR': {'name': 'Arkansas'}, - 'AS': {'name': 'American Samoa'}, - 'AZ': {'name': 'Arizona'}, - 'CA': {'name': 'California'}, - 'CO': {'name': 'Colorado'}, - 'CT': {'name': 'Connecticut'}, - 'DC': {'name': 'District of Columbia'}, - 'DE': {'name': 'Delaware'}, - 'FL': {'name': 'Florida'}, - 'GA': {'name': 'Georgia'}, - 'GU': {'name': 'Guam'}, - 'HI': {'name': 'Hawaii'}, - 'IA': {'name': 'Iowa'}, - 'ID': {'name': 'Idaho'}, - 'IL': {'name': 'Illinois'}, - 'IN': {'name': 'Indiana'}, - 'KS': {'name': 'Kansas'}, - 'KY': {'name': 'Kentucky'}, - 'LA': {'name': 'Louisiana'}, - 'MA': {'name': 'Massachusetts'}, - 'MD': {'name': 'Maryland'}, - 'ME': {'name': 'Maine'}, - 'MI': {'name': 'Michigan'}, - 'MN': {'name': 'Minnesota'}, - 'MO': {'name': 'Missouri'}, - 'MP': {'name': 'Northern Mariana Islands'}, - 'MS': {'name': 'Mississippi'}, - 'MT': {'name': 'Montana'}, - 'NC': {'name': 'North Carolina'}, - 'ND': {'name': 'North Dakota'}, - 'NE': {'name': 'Nebraska'}, - 'NH': {'name': 'New Hampshire'}, - 'NJ': {'name': 'New Jersey'}, - 'NM': {'name': 'New Mexico'}, - 'NV': {'name': 'Nevada'}, - 'NY': {'name': 'New York'}, - 'OH': {'name': 'Ohio'}, - 'OK': {'name': 'Oklahoma'}, - 'OR': {'name': 'Oregon'}, - 'PA': {'name': 'Pennsylvania'}, - 'PR': {'name': 'Puerto Rico'}, - 'RI': {'name': 'Rhode Island'}, - 'SC': {'name': 'South Carolina'}, - 'SD': {'name': 'South Dakota'}, - 'TN': {'name': 'Tennessee'}, - 'TX': {'name': 'Texas'}, - 'UM': {'name': 'United States Minor Outlying ' - 'Islands'}, - 'UT': {'name': 'Utah'}, - 'VA': {'name': 'Virginia'}, - 'VI': {'name': 'Virgin Islands'}, - 'VT': {'name': 'Vermont'}, - 'WA': {'name': 'Washington'}, - 'WI': {'name': 'Wisconsin'}, - 'WV': {'name': 'West Virginia'}, - 'WY': {'name': 'Wyoming'}}}, - 'VC': {'name': 'Saint Vincent and the Grenadines'}, - 'VG': {'name': 'Virgin Islands, British'}, - 'VI': {'name': 'Virgin Islands, U.S.'}}, - 'OC': {'AS': {'name': 'American Samoa'}, - 'AU': {'name': 'Australia'}, - 'CK': {'name': 'Cook Islands'}, - 'FJ': {'name': 'Fiji'}, - 'FM': {'name': 'Micronesia, Federated States of'}, - 'GU': {'name': 'Guam'}, - 'KI': {'name': 'Kiribati'}, - 'MH': {'name': 'Marshall Islands'}, - 'MP': {'name': 'Northern Mariana Islands'}, - 'NC': {'name': 'New Caledonia'}, - 'NF': {'name': 'Norfolk Island'}, - 'NR': {'name': 'Nauru'}, - 'NU': {'name': 'Niue'}, - 'NZ': {'name': 'New Zealand'}, - 'PF': {'name': 'French Polynesia'}, - 'PG': {'name': 'Papua New Guinea'}, - 'PN': {'name': 'Pitcairn'}, - 'PW': {'name': 'Palau'}, - 'SB': {'name': 'Solomon Islands'}, - 'TK': {'name': 'Tokelau'}, - 'TO': {'name': 'Tonga'}, - 'TV': {'name': 'Tuvalu'}, - 'UM': {'name': 'United States Minor Outlying Islands'}, - 'VU': {'name': 'Vanuatu'}, - 'WF': {'name': 'Wallis and Futuna'}, - 'WS': {'name': 'Samoa'}}, - 'SA': {'AR': {'name': 'Argentina'}, - 'BO': {'name': 'Bolivia, Plurinational State of'}, - 'BR': {'name': 'Brazil'}, - 'CL': {'name': 'Chile'}, - 'CO': {'name': 'Colombia'}, - 'EC': {'name': 'Ecuador'}, - 'FK': {'name': 'Falkland Islands (Malvinas)'}, - 'GF': {'name': 'French Guiana'}, - 'GS': {'name': 'South Georgia and the South Sandwich Islands'}, - 'GY': {'name': 'Guyana'}, - 'PE': {'name': 'Peru'}, - 'PY': {'name': 'Paraguay'}, - 'SR': {'name': 'Suriname'}, - 'UY': {'name': 'Uruguay'}, - 'VE': {'name': 'Venezuela, Bolivarian Republic of'}}} +geo_data = { + 'AF': { + 'AO': {'name': 'Angola'}, + 'BF': {'name': 'Burkina Faso'}, + 'BI': {'name': 'Burundi'}, + 'BJ': {'name': 'Benin'}, + 'BW': {'name': 'Botswana'}, + 'CD': {'name': 'Congo, The Democratic Republic of the'}, + 'CF': {'name': 'Central African Republic'}, + 'CG': {'name': 'Congo'}, + 'CI': {'name': "Côte d'Ivoire"}, + 'CM': {'name': 'Cameroon'}, + 'CV': {'name': 'Cabo Verde'}, + 'DJ': {'name': 'Djibouti'}, + 'DZ': {'name': 'Algeria'}, + 'EG': {'name': 'Egypt'}, + 'EH': {'name': 'Western Sahara'}, + 'ER': {'name': 'Eritrea'}, + 'ET': {'name': 'Ethiopia'}, + 'GA': {'name': 'Gabon'}, + 'GH': {'name': 'Ghana'}, + 'GM': {'name': 'Gambia'}, + 'GN': {'name': 'Guinea'}, + 'GQ': {'name': 'Equatorial Guinea'}, + 'GW': {'name': 'Guinea-Bissau'}, + 'KE': {'name': 'Kenya'}, + 'KM': {'name': 'Comoros'}, + 'LR': {'name': 'Liberia'}, + 'LS': {'name': 'Lesotho'}, + 'LY': {'name': 'Libya'}, + 'MA': {'name': 'Morocco'}, + 'MG': {'name': 'Madagascar'}, + 'ML': {'name': 'Mali'}, + 'MR': {'name': 'Mauritania'}, + 'MU': {'name': 'Mauritius'}, + 'MW': {'name': 'Malawi'}, + 'MZ': {'name': 'Mozambique'}, + 'NA': {'name': 'Namibia'}, + 'NE': {'name': 'Niger'}, + 'NG': {'name': 'Nigeria'}, + 'RE': {'name': 'Réunion'}, + 'RW': {'name': 'Rwanda'}, + 'SC': {'name': 'Seychelles'}, + 'SD': {'name': 'Sudan'}, + 'SH': {'name': 'Saint Helena, Ascension and Tristan da Cunha'}, + 'SL': {'name': 'Sierra Leone'}, + 'SN': {'name': 'Senegal'}, + 'SO': {'name': 'Somalia'}, + 'SS': {'name': 'South Sudan'}, + 'ST': {'name': 'Sao Tome and Principe'}, + 'SZ': {'name': 'Eswatini'}, + 'TD': {'name': 'Chad'}, + 'TG': {'name': 'Togo'}, + 'TN': {'name': 'Tunisia'}, + 'TZ': {'name': 'Tanzania, United Republic of'}, + 'UG': {'name': 'Uganda'}, + 'YT': {'name': 'Mayotte'}, + 'ZA': {'name': 'South Africa'}, + 'ZM': {'name': 'Zambia'}, + 'ZW': {'name': 'Zimbabwe'}, + }, + 'AN': { + 'AQ': {'name': 'Antarctica'}, + 'BV': {'name': 'Bouvet Island'}, + 'HM': {'name': 'Heard Island and McDonald Islands'}, + 'TF': {'name': 'French Southern Territories'}, + }, + 'AS': { + 'AE': {'name': 'United Arab Emirates'}, + 'AF': {'name': 'Afghanistan'}, + 'AM': {'name': 'Armenia'}, + 'AZ': {'name': 'Azerbaijan'}, + 'BD': {'name': 'Bangladesh'}, + 'BH': {'name': 'Bahrain'}, + 'BN': {'name': 'Brunei Darussalam'}, + 'BT': {'name': 'Bhutan'}, + 'CC': {'name': 'Cocos (Keeling) Islands'}, + 'CN': {'name': 'China'}, + 'CX': {'name': 'Christmas Island'}, + 'CY': {'name': 'Cyprus'}, + 'GE': {'name': 'Georgia'}, + 'HK': {'name': 'Hong Kong'}, + 'ID': {'name': 'Indonesia'}, + 'IL': {'name': 'Israel'}, + 'IN': {'name': 'India'}, + 'IO': {'name': 'British Indian Ocean Territory'}, + 'IQ': {'name': 'Iraq'}, + 'IR': {'name': 'Iran, Islamic Republic of'}, + 'JO': {'name': 'Jordan'}, + 'JP': {'name': 'Japan'}, + 'KG': {'name': 'Kyrgyzstan'}, + 'KH': {'name': 'Cambodia'}, + 'KP': {'name': "Korea, Democratic People's Republic of"}, + 'KR': {'name': 'Korea, Republic of'}, + 'KW': {'name': 'Kuwait'}, + 'KZ': {'name': 'Kazakhstan'}, + 'LA': {'name': "Lao People's Democratic Republic"}, + 'LB': {'name': 'Lebanon'}, + 'LK': {'name': 'Sri Lanka'}, + 'MM': {'name': 'Myanmar'}, + 'MN': {'name': 'Mongolia'}, + 'MO': {'name': 'Macao'}, + 'MV': {'name': 'Maldives'}, + 'MY': {'name': 'Malaysia'}, + 'NP': {'name': 'Nepal'}, + 'OM': {'name': 'Oman'}, + 'PH': {'name': 'Philippines'}, + 'PK': {'name': 'Pakistan'}, + 'PS': {'name': 'Palestine, State of'}, + 'QA': {'name': 'Qatar'}, + 'SA': {'name': 'Saudi Arabia'}, + 'SG': {'name': 'Singapore'}, + 'SY': {'name': 'Syrian Arab Republic'}, + 'TH': {'name': 'Thailand'}, + 'TJ': {'name': 'Tajikistan'}, + 'TL': {'name': 'Timor-Leste'}, + 'TM': {'name': 'Turkmenistan'}, + 'TR': {'name': 'Turkey'}, + 'TW': {'name': 'Taiwan, Province of China'}, + 'UZ': {'name': 'Uzbekistan'}, + 'VN': {'name': 'Viet Nam'}, + 'YE': {'name': 'Yemen'}, + }, + 'EU': { + 'AD': {'name': 'Andorra'}, + 'AL': {'name': 'Albania'}, + 'AT': {'name': 'Austria'}, + 'AX': {'name': 'Åland Islands'}, + 'BA': {'name': 'Bosnia and Herzegovina'}, + 'BE': {'name': 'Belgium'}, + 'BG': {'name': 'Bulgaria'}, + 'BY': {'name': 'Belarus'}, + 'CH': {'name': 'Switzerland'}, + 'CZ': {'name': 'Czechia'}, + 'DE': {'name': 'Germany'}, + 'DK': {'name': 'Denmark'}, + 'EE': {'name': 'Estonia'}, + 'ES': {'name': 'Spain'}, + 'FI': {'name': 'Finland'}, + 'FO': {'name': 'Faroe Islands'}, + 'FR': {'name': 'France'}, + 'GB': {'name': 'United Kingdom'}, + 'GG': {'name': 'Guernsey'}, + 'GI': {'name': 'Gibraltar'}, + 'GR': {'name': 'Greece'}, + 'HR': {'name': 'Croatia'}, + 'HU': {'name': 'Hungary'}, + 'IE': {'name': 'Ireland'}, + 'IM': {'name': 'Isle of Man'}, + 'IS': {'name': 'Iceland'}, + 'IT': {'name': 'Italy'}, + 'JE': {'name': 'Jersey'}, + 'LI': {'name': 'Liechtenstein'}, + 'LT': {'name': 'Lithuania'}, + 'LU': {'name': 'Luxembourg'}, + 'LV': {'name': 'Latvia'}, + 'MC': {'name': 'Monaco'}, + 'MD': {'name': 'Moldova, Republic of'}, + 'ME': {'name': 'Montenegro'}, + 'MK': {'name': 'North Macedonia'}, + 'MT': {'name': 'Malta'}, + 'NL': {'name': 'Netherlands'}, + 'NO': {'name': 'Norway'}, + 'PL': {'name': 'Poland'}, + 'PT': {'name': 'Portugal'}, + 'RO': {'name': 'Romania'}, + 'RS': {'name': 'Serbia'}, + 'RU': {'name': 'Russian Federation'}, + 'SE': {'name': 'Sweden'}, + 'SI': {'name': 'Slovenia'}, + 'SJ': {'name': 'Svalbard and Jan Mayen'}, + 'SK': {'name': 'Slovakia'}, + 'SM': {'name': 'San Marino'}, + 'UA': {'name': 'Ukraine'}, + 'VA': {'name': 'Holy See (Vatican City State)'}, + }, + 'NA': { + 'AG': {'name': 'Antigua and Barbuda'}, + 'AI': {'name': 'Anguilla'}, + 'AW': {'name': 'Aruba'}, + 'BB': {'name': 'Barbados'}, + 'BL': {'name': 'Saint Barthélemy'}, + 'BM': {'name': 'Bermuda'}, + 'BQ': {'name': 'Bonaire, Sint Eustatius and Saba'}, + 'BS': {'name': 'Bahamas'}, + 'BZ': {'name': 'Belize'}, + 'CA': { + 'name': 'Canada', + 'provinces': { + 'AB': {'name': 'Alberta'}, + 'BC': {'name': 'British Columbia'}, + 'MB': {'name': 'Manitoba'}, + 'NB': {'name': 'New Brunswick'}, + 'NL': {'name': 'Newfoundland and Labrador'}, + 'NS': {'name': 'Nova Scotia'}, + 'NT': {'name': 'Northwest Territories'}, + 'NU': {'name': 'Nunavut'}, + 'ON': {'name': 'Ontario'}, + 'PE': {'name': 'Prince Edward Island'}, + 'QC': {'name': 'Quebec'}, + 'SK': {'name': 'Saskatchewan'}, + 'YT': {'name': 'Yukon Territory'}, + }, + }, + 'CR': {'name': 'Costa Rica'}, + 'CU': {'name': 'Cuba'}, + 'CW': {'name': 'Curaçao'}, + 'DM': {'name': 'Dominica'}, + 'DO': {'name': 'Dominican Republic'}, + 'GD': {'name': 'Grenada'}, + 'GL': {'name': 'Greenland'}, + 'GP': {'name': 'Guadeloupe'}, + 'GT': {'name': 'Guatemala'}, + 'HN': {'name': 'Honduras'}, + 'HT': {'name': 'Haiti'}, + 'JM': {'name': 'Jamaica'}, + 'KN': {'name': 'Saint Kitts and Nevis'}, + 'KY': {'name': 'Cayman Islands'}, + 'LC': {'name': 'Saint Lucia'}, + 'MF': {'name': 'Saint Martin (French part)'}, + 'MQ': {'name': 'Martinique'}, + 'MS': {'name': 'Montserrat'}, + 'MX': {'name': 'Mexico'}, + 'NI': {'name': 'Nicaragua'}, + 'PA': {'name': 'Panama'}, + 'PM': {'name': 'Saint Pierre and Miquelon'}, + 'PR': {'name': 'Puerto Rico'}, + 'SV': {'name': 'El Salvador'}, + 'SX': {'name': 'Sint Maarten (Dutch part)'}, + 'TC': {'name': 'Turks and Caicos Islands'}, + 'TT': {'name': 'Trinidad and Tobago'}, + 'US': { + 'name': 'United States', + 'provinces': { + 'AK': {'name': 'Alaska'}, + 'AL': {'name': 'Alabama'}, + 'AR': {'name': 'Arkansas'}, + 'AS': {'name': 'American Samoa'}, + 'AZ': {'name': 'Arizona'}, + 'CA': {'name': 'California'}, + 'CO': {'name': 'Colorado'}, + 'CT': {'name': 'Connecticut'}, + 'DC': {'name': 'District of Columbia'}, + 'DE': {'name': 'Delaware'}, + 'FL': {'name': 'Florida'}, + 'GA': {'name': 'Georgia'}, + 'GU': {'name': 'Guam'}, + 'HI': {'name': 'Hawaii'}, + 'IA': {'name': 'Iowa'}, + 'ID': {'name': 'Idaho'}, + 'IL': {'name': 'Illinois'}, + 'IN': {'name': 'Indiana'}, + 'KS': {'name': 'Kansas'}, + 'KY': {'name': 'Kentucky'}, + 'LA': {'name': 'Louisiana'}, + 'MA': {'name': 'Massachusetts'}, + 'MD': {'name': 'Maryland'}, + 'ME': {'name': 'Maine'}, + 'MI': {'name': 'Michigan'}, + 'MN': {'name': 'Minnesota'}, + 'MO': {'name': 'Missouri'}, + 'MP': {'name': 'Northern Mariana Islands'}, + 'MS': {'name': 'Mississippi'}, + 'MT': {'name': 'Montana'}, + 'NC': {'name': 'North Carolina'}, + 'ND': {'name': 'North Dakota'}, + 'NE': {'name': 'Nebraska'}, + 'NH': {'name': 'New Hampshire'}, + 'NJ': {'name': 'New Jersey'}, + 'NM': {'name': 'New Mexico'}, + 'NV': {'name': 'Nevada'}, + 'NY': {'name': 'New York'}, + 'OH': {'name': 'Ohio'}, + 'OK': {'name': 'Oklahoma'}, + 'OR': {'name': 'Oregon'}, + 'PA': {'name': 'Pennsylvania'}, + 'PR': {'name': 'Puerto Rico'}, + 'RI': {'name': 'Rhode Island'}, + 'SC': {'name': 'South Carolina'}, + 'SD': {'name': 'South Dakota'}, + 'TN': {'name': 'Tennessee'}, + 'TX': {'name': 'Texas'}, + 'UM': {'name': 'United States Minor Outlying ' 'Islands'}, + 'UT': {'name': 'Utah'}, + 'VA': {'name': 'Virginia'}, + 'VI': {'name': 'Virgin Islands'}, + 'VT': {'name': 'Vermont'}, + 'WA': {'name': 'Washington'}, + 'WI': {'name': 'Wisconsin'}, + 'WV': {'name': 'West Virginia'}, + 'WY': {'name': 'Wyoming'}, + }, + }, + 'VC': {'name': 'Saint Vincent and the Grenadines'}, + 'VG': {'name': 'Virgin Islands, British'}, + 'VI': {'name': 'Virgin Islands, U.S.'}, + }, + 'OC': { + 'AS': {'name': 'American Samoa'}, + 'AU': {'name': 'Australia'}, + 'CK': {'name': 'Cook Islands'}, + 'FJ': {'name': 'Fiji'}, + 'FM': {'name': 'Micronesia, Federated States of'}, + 'GU': {'name': 'Guam'}, + 'KI': {'name': 'Kiribati'}, + 'MH': {'name': 'Marshall Islands'}, + 'MP': {'name': 'Northern Mariana Islands'}, + 'NC': {'name': 'New Caledonia'}, + 'NF': {'name': 'Norfolk Island'}, + 'NR': {'name': 'Nauru'}, + 'NU': {'name': 'Niue'}, + 'NZ': {'name': 'New Zealand'}, + 'PF': {'name': 'French Polynesia'}, + 'PG': {'name': 'Papua New Guinea'}, + 'PN': {'name': 'Pitcairn'}, + 'PW': {'name': 'Palau'}, + 'SB': {'name': 'Solomon Islands'}, + 'TK': {'name': 'Tokelau'}, + 'TO': {'name': 'Tonga'}, + 'TV': {'name': 'Tuvalu'}, + 'UM': {'name': 'United States Minor Outlying Islands'}, + 'VU': {'name': 'Vanuatu'}, + 'WF': {'name': 'Wallis and Futuna'}, + 'WS': {'name': 'Samoa'}, + }, + 'SA': { + 'AR': {'name': 'Argentina'}, + 'BO': {'name': 'Bolivia, Plurinational State of'}, + 'BR': {'name': 'Brazil'}, + 'CL': {'name': 'Chile'}, + 'CO': {'name': 'Colombia'}, + 'EC': {'name': 'Ecuador'}, + 'FK': {'name': 'Falkland Islands (Malvinas)'}, + 'GF': {'name': 'French Guiana'}, + 'GS': {'name': 'South Georgia and the South Sandwich Islands'}, + 'GY': {'name': 'Guyana'}, + 'PE': {'name': 'Peru'}, + 'PY': {'name': 'Paraguay'}, + 'SR': {'name': 'Suriname'}, + 'UY': {'name': 'Uruguay'}, + 'VE': {'name': 'Venezuela, Bolivarian Republic of'}, + }, +} diff --git a/octodns/source/__init__.py b/octodns/source/__init__.py index 14ccf18..16a8eb0 100644 --- a/octodns/source/__init__.py +++ b/octodns/source/__init__.py @@ -2,5 +2,9 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) diff --git a/octodns/source/axfr.py b/octodns/source/axfr.py index 4c33d97..a6a8be0 100644 --- a/octodns/source/axfr.py +++ b/octodns/source/axfr.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) import dns.name import dns.query @@ -25,8 +29,22 @@ class AxfrBaseSource(BaseSource): SUPPORTS_GEO = False SUPPORTS_DYNAMIC = False - SUPPORTS = set(('A', 'AAAA', 'CAA', 'CNAME', 'LOC', 'MX', 'NS', 'PTR', - 'SPF', 'SRV', 'SSHFP', 'TXT')) + SUPPORTS = set( + ( + 'A', + 'AAAA', + 'CAA', + 'CNAME', + 'LOC', + 'MX', + 'NS', + 'PTR', + 'SPF', + 'SRV', + 'SSHFP', + 'TXT', + ) + ) def __init__(self, id): super(AxfrBaseSource, self).__init__(id) @@ -35,7 +53,7 @@ class AxfrBaseSource(BaseSource): return { 'ttl': records[0]['ttl'], 'type': _type, - 'values': [r['value'] for r in records] + 'values': [r['value'] for r in records], } _data_for_A = _data_for_multiple @@ -46,75 +64,62 @@ class AxfrBaseSource(BaseSource): values = [] for record in records: flags, tag, value = record['value'].split(' ', 2) - values.append({ - 'flags': flags, - 'tag': tag, - 'value': value.replace('"', '') - }) - return { - 'ttl': records[0]['ttl'], - 'type': _type, - 'values': values - } + values.append( + {'flags': flags, 'tag': tag, 'value': value.replace('"', '')} + ) + return {'ttl': records[0]['ttl'], 'type': _type, 'values': values} def _data_for_LOC(self, _type, records): values = [] for record in records: - lat_degrees, lat_minutes, lat_seconds, lat_direction, \ - long_degrees, long_minutes, long_seconds, long_direction, \ - altitude, size, precision_horz, precision_vert = \ - record['value'].replace('m', '').split(' ', 11) - values.append({ - 'lat_degrees': lat_degrees, - 'lat_minutes': lat_minutes, - 'lat_seconds': lat_seconds, - 'lat_direction': lat_direction, - 'long_degrees': long_degrees, - 'long_minutes': long_minutes, - 'long_seconds': long_seconds, - 'long_direction': long_direction, - 'altitude': altitude, - 'size': size, - 'precision_horz': precision_horz, - 'precision_vert': precision_vert, - }) - return { - 'ttl': records[0]['ttl'], - 'type': _type, - 'values': values - } + ( + lat_degrees, + lat_minutes, + lat_seconds, + lat_direction, + long_degrees, + long_minutes, + long_seconds, + long_direction, + altitude, + size, + precision_horz, + precision_vert, + ) = (record['value'].replace('m', '').split(' ', 11)) + values.append( + { + 'lat_degrees': lat_degrees, + 'lat_minutes': lat_minutes, + 'lat_seconds': lat_seconds, + 'lat_direction': lat_direction, + 'long_degrees': long_degrees, + 'long_minutes': long_minutes, + 'long_seconds': long_seconds, + 'long_direction': long_direction, + 'altitude': altitude, + 'size': size, + 'precision_horz': precision_horz, + 'precision_vert': precision_vert, + } + ) + return {'ttl': records[0]['ttl'], 'type': _type, 'values': values} def _data_for_MX(self, _type, records): values = [] for record in records: preference, exchange = record['value'].split(' ', 1) - values.append({ - 'preference': preference, - 'exchange': exchange, - }) - return { - 'ttl': records[0]['ttl'], - 'type': _type, - 'values': values - } + values.append({'preference': preference, 'exchange': exchange}) + return {'ttl': records[0]['ttl'], 'type': _type, 'values': values} def _data_for_TXT(self, _type, records): values = [value['value'].replace(';', '\\;') for value in records] - return { - 'ttl': records[0]['ttl'], - 'type': _type, - 'values': values - } + return {'ttl': records[0]['ttl'], 'type': _type, 'values': values} _data_for_SPF = _data_for_TXT def _data_for_single(self, _type, records): record = records[0] - return { - 'ttl': record['ttl'], - 'type': _type, - 'value': record['value'] - } + return {'ttl': record['ttl'], 'type': _type, 'value': record['value']} _data_for_CNAME = _data_for_single _data_for_PTR = _data_for_single @@ -123,37 +128,38 @@ class AxfrBaseSource(BaseSource): values = [] for record in records: priority, weight, port, target = record['value'].split(' ', 3) - values.append({ - 'priority': priority, - 'weight': weight, - 'port': port, - 'target': target, - }) - return { - 'type': _type, - 'ttl': records[0]['ttl'], - 'values': values - } + values.append( + { + 'priority': priority, + 'weight': weight, + 'port': port, + 'target': target, + } + ) + return {'type': _type, 'ttl': records[0]['ttl'], 'values': values} def _data_for_SSHFP(self, _type, records): values = [] for record in records: - algorithm, fingerprint_type, fingerprint = \ - record['value'].split(' ', 2) - values.append({ - 'algorithm': algorithm, - 'fingerprint_type': fingerprint_type, - 'fingerprint': fingerprint, - }) - return { - 'type': _type, - 'ttl': records[0]['ttl'], - 'values': values - } + algorithm, fingerprint_type, fingerprint = record['value'].split( + ' ', 2 + ) + values.append( + { + 'algorithm': algorithm, + 'fingerprint_type': fingerprint_type, + 'fingerprint': fingerprint, + } + ) + return {'type': _type, 'ttl': records[0]['ttl'], 'values': values} def populate(self, zone, target=False, lenient=False): - self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, - target, lenient) + self.log.debug( + 'populate: name=%s, target=%s, lenient=%s', + zone.name, + target, + lenient, + ) values = defaultdict(lambda: defaultdict(list)) for record in self.zone_records(zone): @@ -167,12 +173,18 @@ class AxfrBaseSource(BaseSource): for name, types in values.items(): for _type, records in types.items(): data_for = getattr(self, f'_data_for_{_type}') - record = Record.new(zone, name, data_for(_type, records), - source=self, lenient=lenient) + record = Record.new( + zone, + name, + data_for(_type, records), + source=self, + lenient=lenient, + ) zone.add_record(record, lenient=lenient) - self.log.info('populate: found %s records', - len(zone.records) - before) + self.log.info( + 'populate: found %s records', len(zone.records) - before + ) class AxfrSourceException(Exception): @@ -180,10 +192,10 @@ class AxfrSourceException(Exception): class AxfrSourceZoneTransferFailed(AxfrSourceException): - def __init__(self): super(AxfrSourceZoneTransferFailed, self).__init__( - 'Unable to Perform Zone Transfer') + 'Unable to Perform Zone Transfer' + ) class AxfrSource(AxfrBaseSource): @@ -195,6 +207,7 @@ class AxfrSource(AxfrBaseSource): # The address of nameserver to perform zone transfer against master: ns1.example.com ''' + def __init__(self, id, master): self.log = logging.getLogger(f'AxfrSource[{id}]') self.log.debug('__init__: id=%s, master=%s', id, master) @@ -203,9 +216,10 @@ class AxfrSource(AxfrBaseSource): def zone_records(self, zone): try: - z = dns.zone.from_xfr(dns.query.xfr(self.master, zone.name, - relativize=False), - relativize=False) + z = dns.zone.from_xfr( + dns.query.xfr(self.master, zone.name, relativize=False), + relativize=False, + ) except DNSException: raise AxfrSourceZoneTransferFailed() @@ -213,12 +227,14 @@ class AxfrSource(AxfrBaseSource): for (name, ttl, rdata) in z.iterate_rdatas(): rdtype = dns.rdatatype.to_text(rdata.rdtype) - records.append({ - "name": name.to_text(), - "ttl": ttl, - "type": rdtype, - "value": rdata.to_text() - }) + records.append( + { + "name": name.to_text(), + "ttl": ttl, + "type": rdtype, + "value": rdata.to_text(), + } + ) return records @@ -228,14 +244,11 @@ class ZoneFileSourceException(Exception): class ZoneFileSourceNotFound(ZoneFileSourceException): - def __init__(self): - super(ZoneFileSourceNotFound, self).__init__( - 'Zone file not found') + super(ZoneFileSourceNotFound, self).__init__('Zone file not found') class ZoneFileSourceLoadFailure(ZoneFileSourceException): - def __init__(self, error): super(ZoneFileSourceLoadFailure, self).__init__(str(error)) @@ -258,11 +271,17 @@ class ZoneFileSource(AxfrBaseSource): # (optional, default true) check_origin: false ''' + def __init__(self, id, directory, file_extension='.', check_origin=True): self.log = logging.getLogger(f'ZoneFileSource[{id}]') - self.log.debug('__init__: id=%s, directory=%s, file_extension=%s, ' - 'check_origin=%s', id, - directory, file_extension, check_origin) + self.log.debug( + '__init__: id=%s, directory=%s, file_extension=%s, ' + 'check_origin=%s', + id, + directory, + file_extension, + check_origin, + ) super(ZoneFileSource, self).__init__(id) self.directory = directory self.file_extension = file_extension @@ -275,9 +294,12 @@ class ZoneFileSource(AxfrBaseSource): zonefiles = listdir(self.directory) if zone_filename in zonefiles: try: - z = dns.zone.from_file(join(self.directory, zone_filename), - zone_name, relativize=False, - check_origin=self.check_origin) + z = dns.zone.from_file( + join(self.directory, zone_filename), + zone_name, + relativize=False, + check_origin=self.check_origin, + ) except DNSException as error: raise ZoneFileSourceLoadFailure(error) else: @@ -292,12 +314,14 @@ class ZoneFileSource(AxfrBaseSource): records = [] for (name, ttl, rdata) in z.iterate_rdatas(): rdtype = dns.rdatatype.to_text(rdata.rdtype) - records.append({ - "name": name.to_text(), - "ttl": ttl, - "type": rdtype, - "value": rdata.to_text() - }) + records.append( + { + "name": name.to_text(), + "ttl": ttl, + "type": rdtype, + "value": rdata.to_text(), + } + ) self._zone_records[zone.name] = records except ZoneFileSourceNotFound: diff --git a/octodns/source/base.py b/octodns/source/base.py index e328b04..fdd2bbc 100644 --- a/octodns/source/base.py +++ b/octodns/source/base.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) class BaseSource(object): @@ -15,14 +19,17 @@ class BaseSource(object): def __init__(self, id): self.id = id if not getattr(self, 'log', False): - raise NotImplementedError('Abstract base class, log property ' - 'missing') + raise NotImplementedError( + 'Abstract base class, log property ' 'missing' + ) if not hasattr(self, 'SUPPORTS_GEO'): - raise NotImplementedError('Abstract base class, SUPPORTS_GEO ' - 'property missing') + raise NotImplementedError( + 'Abstract base class, SUPPORTS_GEO ' 'property missing' + ) if not hasattr(self, 'SUPPORTS'): - raise NotImplementedError('Abstract base class, SUPPORTS ' - 'property missing') + raise NotImplementedError( + 'Abstract base class, SUPPORTS ' 'property missing' + ) @property def SUPPORTS_DYNAMIC(self): @@ -43,8 +50,9 @@ class BaseSource(object): When target is True (loading current state) this method should return True if the zone exists or False if it does not. ''' - raise NotImplementedError('Abstract base class, populate method ' - 'missing') + raise NotImplementedError( + 'Abstract base class, populate method ' 'missing' + ) def supports(self, record): return record._type in self.SUPPORTS diff --git a/octodns/source/envvar.py b/octodns/source/envvar.py index 9f54b3b..95c5ffc 100644 --- a/octodns/source/envvar.py +++ b/octodns/source/envvar.py @@ -1,4 +1,3 @@ - import logging import os @@ -13,7 +12,8 @@ class EnvVarSourceException(Exception): class EnvironmentVariableNotFoundException(EnvVarSourceException): def __init__(self, data): super(EnvironmentVariableNotFoundException, self).__init__( - f'Unknown environment variable {data}') + f'Unknown environment variable {data}' + ) class EnvVarSource(BaseSource): @@ -56,6 +56,7 @@ class EnvVarSource(BaseSource): - ultra - ns1 ''' + SUPPORTS_GEO = False SUPPORTS_DYNAMIC = False SUPPORTS = set(('TXT')) @@ -65,8 +66,13 @@ class EnvVarSource(BaseSource): def __init__(self, id, variable, name, ttl=DEFAULT_TTL): klass = self.__class__.__name__ self.log = logging.getLogger(f'{klass}[{id}]') - self.log.debug('__init__: id=%s, variable=%s, name=%s, ' - 'ttl=%d', id, variable, name, ttl) + self.log.debug( + '__init__: id=%s, variable=%s, name=%s, ' 'ttl=%d', + id, + variable, + name, + ttl, + ) super(EnvVarSource, self).__init__(id) self.envvar = variable self.name = name @@ -77,13 +83,20 @@ class EnvVarSource(BaseSource): if value is None: raise EnvironmentVariableNotFoundException(self.envvar) - self.log.debug('_read_variable: successfully loaded var=%s val=%s', - self.envvar, value) + self.log.debug( + '_read_variable: successfully loaded var=%s val=%s', + self.envvar, + value, + ) return value def populate(self, zone, target=False, lenient=False): - self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, - target, lenient) + self.log.debug( + 'populate: name=%s, target=%s, lenient=%s', + zone.name, + target, + lenient, + ) before = len(zone.records) @@ -92,9 +105,12 @@ class EnvVarSource(BaseSource): # We don't need to worry about conflicting records here because the # manager will deconflict sources on our behalf. payload = {'ttl': self.ttl, 'type': 'TXT', 'values': [value]} - record = Record.new(zone, self.name, payload, source=self, - lenient=lenient) + record = Record.new( + zone, self.name, payload, source=self, lenient=lenient + ) zone.add_record(record, lenient=lenient) - self.log.info('populate: found %s records, exists=False', - len(zone.records) - before) + self.log.info( + 'populate: found %s records, exists=False', + len(zone.records) - before, + ) diff --git a/octodns/source/tinydns.py b/octodns/source/tinydns.py index a5c72b8..19bf734 100755 --- a/octodns/source/tinydns.py +++ b/octodns/source/tinydns.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from collections import defaultdict from ipaddress import ip_address @@ -40,11 +44,7 @@ class TinyDnsBaseSource(BaseSource): ttl = records[0][1] except IndexError: ttl = self.default_ttl - return { - 'ttl': ttl, - 'type': _type, - 'values': values, - } + return {'ttl': ttl, 'type': _type, 'values': values} def _data_for_AAAA(self, _type, records): values = [] @@ -57,29 +57,25 @@ class TinyDnsBaseSource(BaseSource): ttl = records[0][1] except IndexError: ttl = self.default_ttl - return { - 'ttl': ttl, - 'type': _type, - 'values': values, - } + return {'ttl': ttl, 'type': _type, 'values': values} def _data_for_TXT(self, _type, records): values = [] for record in records: - new_value = record[0].encode('latin1').decode('unicode-escape') \ + new_value = ( + record[0] + .encode('latin1') + .decode('unicode-escape') .replace(";", "\\;") + ) values.append(new_value) try: ttl = records[0][1] except IndexError: ttl = self.default_ttl - return { - 'ttl': ttl, - 'type': _type, - 'values': values, - } + return {'ttl': ttl, 'type': _type, 'values': values} def _data_for_CNAME(self, _type, records): first = records[0] @@ -87,11 +83,7 @@ class TinyDnsBaseSource(BaseSource): ttl = first[1] except IndexError: ttl = self.default_ttl - return { - 'ttl': ttl, - 'type': _type, - 'value': f'{first[0]}.' - } + return {'ttl': ttl, 'type': _type, 'value': f'{first[0]}.'} def _data_for_MX(self, _type, records): try: @@ -101,10 +93,9 @@ class TinyDnsBaseSource(BaseSource): return { 'ttl': ttl, 'type': _type, - 'values': [{ - 'preference': r[1], - 'exchange': f'{r[0]}.' - } for r in records] + 'values': [ + {'preference': r[1], 'exchange': f'{r[0]}.'} for r in records + ], } def _data_for_NS(self, _type, records): @@ -115,12 +106,16 @@ class TinyDnsBaseSource(BaseSource): return { 'ttl': ttl, 'type': _type, - 'values': [f'{r[0]}.' for r in records] + 'values': [f'{r[0]}.' for r in records], } def populate(self, zone, target=False, lenient=False): - self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, - target, lenient) + self.log.debug( + 'populate: name=%s, target=%s, lenient=%s', + zone.name, + target, + lenient, + ) before = len(zone.records) @@ -129,8 +124,9 @@ class TinyDnsBaseSource(BaseSource): else: self._populate_normal(zone, lenient) - self.log.info('populate: found %s records', - len(zone.records) - before) + self.log.info( + 'populate: found %s records', len(zone.records) - before + ) def _populate_normal(self, zone, lenient): type_map = { @@ -171,13 +167,16 @@ class TinyDnsBaseSource(BaseSource): data_for = getattr(self, f'_data_for_{_type}') data = data_for(_type, d) if data: - record = Record.new(zone, name, data, source=self, - lenient=lenient) + record = Record.new( + zone, name, data, source=self, lenient=lenient + ) try: zone.add_record(record, lenient=lenient) except SubzoneRecordException: - self.log.debug('_populate_normal: skipping subzone ' - 'record=%s', record) + self.log.debug( + '_populate_normal: skipping subzone ' 'record=%s', + record, + ) def _populate_in_addr_arpa(self, zone, lenient): name_re = re.compile(fr'(?P.+)\.{zone.name[:-1]}$') @@ -209,11 +208,13 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl name = match.group('name') - record = Record.new(zone, name, { - 'ttl': ttl, - 'type': 'PTR', - 'value': value - }, source=self, lenient=lenient) + record = Record.new( + zone, + name, + {'ttl': ttl, 'type': 'PTR', 'value': value}, + source=self, + lenient=lenient, + ) try: zone.add_record(record, lenient=lenient) except DuplicateRecordException: @@ -236,10 +237,15 @@ class TinyDnsFileSource(TinyDnsBaseSource): NOTE: timestamps & lo fields are ignored if present. ''' + def __init__(self, id, directory, default_ttl=3600): self.log = logging.getLogger(f'TinyDnsFileSource[{id}]') - self.log.debug('__init__: id=%s, directory=%s, default_ttl=%d', id, - directory, default_ttl) + self.log.debug( + '__init__: id=%s, directory=%s, default_ttl=%d', + id, + directory, + default_ttl, + ) super(TinyDnsFileSource, self).__init__(id, default_ttl) self.directory = directory self._cache = None diff --git a/octodns/yaml.py b/octodns/yaml.py index 7e0101c..5b02431 100644 --- a/octodns/yaml.py +++ b/octodns/yaml.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from natsort import natsort_keygen from yaml import SafeDumper, SafeLoader, load, dump @@ -16,7 +20,6 @@ _natsort_key = natsort_keygen() # Found http://stackoverflow.com/a/21912744 which guided me on how to hook in # here class SortEnforcingLoader(SafeLoader): - def _construct(self, node): self.flatten_mapping(node) ret = self.construct_pairs(node) @@ -25,14 +28,18 @@ class SortEnforcingLoader(SafeLoader): for key in keys: expected = keys_sorted.pop(0) if key != expected: - raise ConstructorError(None, None, 'keys out of order: ' - f'expected {expected} got {key} at ' + - str(node.start_mark)) + raise ConstructorError( + None, + None, + 'keys out of order: ' + f'expected {expected} got {key} at ' + str(node.start_mark), + ) return dict(ret) -SortEnforcingLoader.add_constructor(SortEnforcingLoader.DEFAULT_MAPPING_TAG, - SortEnforcingLoader._construct) +SortEnforcingLoader.add_constructor( + SortEnforcingLoader.DEFAULT_MAPPING_TAG, SortEnforcingLoader._construct +) def safe_load(stream, enforce_order=True): @@ -62,7 +69,7 @@ def safe_dump(data, fh, **options): 'indent': 2, 'default_style': '', 'default_flow_style': False, - 'explicit_start': True + 'explicit_start': True, } kwargs.update(options) dump(data, fh, SortingDumper, **kwargs) diff --git a/octodns/zone.py b/octodns/zone.py index 7e42fa3..f93b373 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from collections import defaultdict from logging import getLogger @@ -72,13 +76,16 @@ class Zone(object): if not lenient and any((name.endswith(sz) for sz in self.sub_zones)): if name not in self.sub_zones: # it's a record for something under a sub-zone - raise SubzoneRecordException(f'Record {record.fqdn} is under ' - 'a managed subzone') + raise SubzoneRecordException( + f'Record {record.fqdn} is under ' 'a managed subzone' + ) elif record._type != 'NS': # It's a non NS record for exactly a sub-zone - raise SubzoneRecordException(f'Record {record.fqdn} a ' - 'managed sub-zone and not of ' - 'type NS') + raise SubzoneRecordException( + f'Record {record.fqdn} a ' + 'managed sub-zone and not of ' + 'type NS' + ) if replace: # will remove it if it exists @@ -87,15 +94,20 @@ class Zone(object): node = self._records[name] if record in node: # We already have a record at this node of this type - raise DuplicateRecordException(f'Duplicate record {record.fqdn}, ' - f'type {record._type}') - elif not lenient and ((record._type == 'CNAME' and len(node) > 0) or - ('CNAME' in [r._type for r in node])): + raise DuplicateRecordException( + f'Duplicate record {record.fqdn}, ' f'type {record._type}' + ) + elif not lenient and ( + (record._type == 'CNAME' and len(node) > 0) + or ('CNAME' in [r._type for r in node]) + ): # 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') + raise InvalidNodeException( + 'Invalid state, CNAME at ' + f'{record.fqdn} cannot coexist with ' + 'other records' + ) if record._type == 'NS' and record.name == '': self._root_ns = record @@ -128,49 +140,69 @@ class Zone(object): for record in self.records: if record.ignored: continue - elif len(record.included) > 0 and \ - target.id not in record.included: - self.log.debug('changes: skipping record=%s %s - %s not' - ' included ', record.fqdn, record._type, - target.id) + elif len(record.included) > 0 and target.id not in record.included: + self.log.debug( + 'changes: skipping record=%s %s - %s not' ' included ', + record.fqdn, + record._type, + target.id, + ) continue elif target.id in record.excluded: - self.log.debug('changes: skipping record=%s %s - %s ' - 'excluded ', record.fqdn, record._type, - target.id) + self.log.debug( + 'changes: skipping record=%s %s - %s ' 'excluded ', + record.fqdn, + record._type, + target.id, + ) continue try: desired_record = desired_records[record] if desired_record.ignored: continue - elif len(desired_record.included) > 0 and \ - target.id not in desired_record.included: - self.log.debug('changes: skipping record=%s %s - %s' - 'not included ', record.fqdn, record._type, - target.id) + elif ( + len(desired_record.included) > 0 + and target.id not in desired_record.included + ): + self.log.debug( + 'changes: skipping record=%s %s - %s' 'not included ', + record.fqdn, + record._type, + target.id, + ) continue elif target.id in desired_record.excluded: continue except KeyError: if not target.supports(record): - self.log.debug('changes: skipping record=%s %s - %s does ' - 'not support it', record.fqdn, record._type, - target.id) + self.log.debug( + 'changes: skipping record=%s %s - %s does ' + 'not support it', + record.fqdn, + record._type, + target.id, + ) continue # record has been removed - self.log.debug('changes: zone=%s, removed record=%s', self, - record) + self.log.debug( + 'changes: zone=%s, removed record=%s', self, record + ) changes.append(Delete(record)) else: change = record.changes(desired_record, target) if change: - self.log.debug('changes: zone=%s, modified\n' - ' existing=%s,\n desired=%s', self, - record, desired_record) + self.log.debug( + 'changes: zone=%s, modified\n' + ' existing=%s,\n desired=%s', + self, + record, + desired_record, + ) changes.append(change) else: - self.log.debug('changes: zone=%s, n.c. record=%s', self, - record) + self.log.debug( + 'changes: zone=%s, n.c. record=%s', self, record + ) # Find additions, things that are in desired, but missing in ourselves. # This uses set math and our special __hash__ and __cmp__ functions as @@ -178,22 +210,31 @@ class Zone(object): for record in desired.records - self.records: if record.ignored: continue - elif len(record.included) > 0 and \ - target.id not in record.included: - self.log.debug('changes: skipping record=%s %s - %s not' - ' included ', record.fqdn, record._type, - target.id) + elif len(record.included) > 0 and target.id not in record.included: + self.log.debug( + 'changes: skipping record=%s %s - %s not' ' included ', + record.fqdn, + record._type, + target.id, + ) continue elif target.id in record.excluded: - self.log.debug('changes: skipping record=%s %s - %s ' - 'excluded ', record.fqdn, record._type, - target.id) + self.log.debug( + 'changes: skipping record=%s %s - %s ' 'excluded ', + record.fqdn, + record._type, + target.id, + ) continue if not target.supports(record): - self.log.debug('changes: skipping record=%s %s - %s does not ' - 'support it', record.fqdn, record._type, - target.id) + self.log.debug( + 'changes: skipping record=%s %s - %s does not ' + 'support it', + record.fqdn, + record._type, + target.id, + ) continue self.log.debug('changes: zone=%s, create record=%s', self, record) changes.append(Create(record)) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0fec700..7c79e3e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,10 +1,12 @@ Pygments==2.11.2 attrs==21.4.0 +black==22.3.0 bleach==4.1.0 build==0.7.0 certifi==2021.10.8 cffi==1.15.0 charset-normalizer==2.0.12 +click>=8.0.0 cmarkgfm==0.8.0 colorama==0.4.4 coverage==6.3.2 @@ -13,13 +15,15 @@ idna==3.3 importlib-metadata==4.11.2 iniconfig==1.1.1 keyring==23.5.0 +mypy-extensions>=0.4.3 packaging==21.3 +pathspec>=0.9.0 pep517==0.12.0 pkginfo==1.8.2 +platformdirs>=2 pluggy==1.0.0 pprintpp==0.4.0 py==1.11.0 -pycodestyle==2.8.0 pycountry-convert==0.7.2 pycountry==22.3.5 pycparser==2.21 @@ -37,6 +41,7 @@ rfc3986==2.0.0 tomli==2.0.1 tqdm==4.63.0 twine==3.8.0 +typing-extensions>=3.10.0.0 urllib3==1.26.8 webencodings==0.5.1 zipp==3.7.0 diff --git a/script/cibuild b/script/cibuild index 77d7a2c..f50882c 100755 --- a/script/cibuild +++ b/script/cibuild @@ -24,6 +24,8 @@ echo "## begin ################################################################# # For now it's just lint... echo "## lint ########################################################################" script/lint +echo "## formatting ##################################################################" +script/format --check || (echo "Formatting check failed, run ./script/format" && exit 1) echo "## tests/coverage ##############################################################" script/coverage echo "## complete ####################################################################" diff --git a/script/format b/script/format new file mode 100755 index 0000000..51ad3b9 --- /dev/null +++ b/script/format @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +SOURCES=$(find *.py octodns tests -name "*.py") + +. env/bin/activate + +black --line-length=80 --skip-string-normalization --skip-magic-trailing-comma "$@" $SOURCES diff --git a/script/lint b/script/lint index 627c5be..24c7643 100755 --- a/script/lint +++ b/script/lint @@ -17,5 +17,4 @@ fi SOURCES="*.py octodns/*.py octodns/*/*.py tests/*.py" -pycodestyle --ignore=E221,E241,E251,E722,E741,W504 $SOURCES pyflakes $SOURCES diff --git a/setup.py b/setup.py index 3d5faf7..dcef861 100644 --- a/setup.py +++ b/setup.py @@ -11,14 +11,7 @@ try: except ImportError: from distutils.core import find_packages, setup -cmds = ( - 'compare', - 'dump', - 'report', - 'sync', - 'validate', - 'versions', -) +cmds = ('compare', 'dump', 'report', 'sync', 'validate', 'versions') cmds_dir = join(dirname(__file__), 'octodns', 'cmds') console_scripts = { 'octodns-{name} = octodns.cmds.{name}:main'.format(name=name) @@ -68,29 +61,24 @@ def version(): return f'{octodns.__VERSION__}+{sha}' -tests_require = ( - 'pytest>=6.2.5', - 'pytest-cov>=3.0.0', - 'pytest-network>=0.0.1', -) +tests_require = ('pytest>=6.2.5', 'pytest-cov>=3.0.0', 'pytest-network>=0.0.1') setup( author='Ross McFarland', author_email='rwmcfa1@gmail.com', description=octodns.__doc__, - entry_points={ - 'console_scripts': console_scripts, - }, + entry_points={'console_scripts': console_scripts}, extras_require={ - 'dev': tests_require + ( + 'dev': tests_require + + ( + 'black>=22.3.0', 'build>=0.7.0', - 'pycodestyle>=2.6.0', 'pycountry>=19.8.18', 'pycountry-convert>=0.7.2', 'pyflakes>=2.2.0', 'readme_renderer[md]>=26.0', 'twine>=3.4.2', - ), + ) }, install_requires=( 'PyYaml>=4.2b1', diff --git a/tests/helpers.py b/tests/helpers.py index 4f0ddb2..5bb0a86 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from shutil import rmtree from tempfile import mkdtemp @@ -15,7 +19,6 @@ from octodns.provider.yaml import YamlProvider class SimpleSource(object): - def __init__(self, id='test'): pass @@ -78,13 +81,11 @@ class DynamicProvider(object): class NoSshFpProvider(SimpleProvider): - def supports(self, record): return record._type != 'SSHFP' class TemporaryDirectory(object): - def __init__(self, delete_on_exit=True): self.delete_on_exit = delete_on_exit @@ -100,7 +101,6 @@ class TemporaryDirectory(object): class WantsConfigProcessor(BaseProcessor): - def __init__(self, name, some_config): super(WantsConfigProcessor, self).__init__(name) diff --git a/tests/test_octodns_equality.py b/tests/test_octodns_equality.py index 18bed73..71bfaaa 100644 --- a/tests/test_octodns_equality.py +++ b/tests/test_octodns_equality.py @@ -2,8 +2,12 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from unittest import TestCase @@ -11,11 +15,8 @@ from octodns.equality import EqualityTupleMixin class TestEqualityTupleMixin(TestCase): - def test_basics(self): - class Simple(EqualityTupleMixin): - def __init__(self, a, b, c): self.a = a self.b = b @@ -60,7 +61,6 @@ class TestEqualityTupleMixin(TestCase): self.assertTrue(one >= same) def test_not_implemented(self): - class MissingMethod(EqualityTupleMixin): pass diff --git a/tests/test_octodns_manager.py b/tests/test_octodns_manager.py index 8f84e67..44cf3e4 100644 --- a/tests/test_octodns_manager.py +++ b/tests/test_octodns_manager.py @@ -2,15 +2,23 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from os import environ from os.path import dirname, join from octodns import __VERSION__ -from octodns.manager import _AggregateTarget, MainThreadExecutor, Manager, \ - ManagerException +from octodns.manager import ( + _AggregateTarget, + MainThreadExecutor, + Manager, + ManagerException, +) from octodns.processor.base import BaseProcessor from octodns.record import Create, Delete, Record from octodns.yaml import safe_load @@ -19,8 +27,14 @@ from octodns.zone import Zone from unittest import TestCase from unittest.mock import MagicMock, patch -from helpers import DynamicProvider, GeoProvider, NoSshFpProvider, \ - PlannableProvider, SimpleProvider, TemporaryDirectory +from helpers import ( + DynamicProvider, + GeoProvider, + NoSshFpProvider, + PlannableProvider, + SimpleProvider, + TemporaryDirectory, +) config_dir = join(dirname(__file__), 'config') @@ -30,7 +44,6 @@ def get_config_filename(which): class TestManager(TestCase): - def test_missing_provider_class(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('missing-provider-class.yaml')).sync() @@ -43,14 +56,16 @@ class TestManager(TestCase): def test_bad_provider_class_module(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('bad-provider-class-module.yaml')) \ - .sync() + Manager( + get_config_filename('bad-provider-class-module.yaml') + ).sync() self.assertTrue('Unknown provider class' in str(ctx.exception)) def test_bad_provider_class_no_module(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('bad-provider-class-no-module.yaml')) \ - .sync() + Manager( + get_config_filename('bad-provider-class-no-module.yaml') + ).sync() self.assertTrue('Unknown provider class' in str(ctx.exception)) def test_missing_provider_config(self): @@ -66,141 +81,160 @@ class TestManager(TestCase): def test_missing_source(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('provider-problems.yaml')) \ - .sync(['missing.sources.']) + Manager(get_config_filename('provider-problems.yaml')).sync( + ['missing.sources.'] + ) self.assertTrue('missing sources' in str(ctx.exception)) def test_missing_targets(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('provider-problems.yaml')) \ - .sync(['missing.targets.']) + Manager(get_config_filename('provider-problems.yaml')).sync( + ['missing.targets.'] + ) self.assertTrue('missing targets' in str(ctx.exception)) def test_unknown_source(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('provider-problems.yaml')) \ - .sync(['unknown.source.']) + Manager(get_config_filename('provider-problems.yaml')).sync( + ['unknown.source.'] + ) self.assertTrue('unknown source' in str(ctx.exception)) def test_unknown_target(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('provider-problems.yaml')) \ - .sync(['unknown.target.']) + Manager(get_config_filename('provider-problems.yaml')).sync( + ['unknown.target.'] + ) self.assertTrue('unknown target' in str(ctx.exception)) def test_bad_plan_output_class(self): with self.assertRaises(ManagerException) as ctx: name = 'bad-plan-output-missing-class.yaml' Manager(get_config_filename(name)).sync() - self.assertEqual('plan_output bad is missing class', - str(ctx.exception)) + self.assertEqual('plan_output bad is missing class', str(ctx.exception)) def test_bad_plan_output_config(self): with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('bad-plan-output-config.yaml')).sync() - self.assertEqual('Incorrect plan_output config for bad', - str(ctx.exception)) + self.assertEqual( + 'Incorrect plan_output config for bad', str(ctx.exception) + ) def test_source_only_as_a_target(self): with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('provider-problems.yaml')) \ - .sync(['not.targetable.']) - self.assertTrue('does not support targeting' in - str(ctx.exception)) + Manager(get_config_filename('provider-problems.yaml')).sync( + ['not.targetable.'] + ) + self.assertTrue('does not support targeting' in str(ctx.exception)) def test_always_dry_run(self): with TemporaryDirectory() as tmpdir: environ['YAML_TMP_DIR'] = tmpdir.dirname - tc = Manager(get_config_filename('always-dry-run.yaml')) \ - .sync(dry_run=False) + tc = Manager(get_config_filename('always-dry-run.yaml')).sync( + dry_run=False + ) # only the stuff from subzone, unit.tests. is always-dry-run self.assertEqual(3, tc) def test_simple(self): with TemporaryDirectory() as tmpdir: environ['YAML_TMP_DIR'] = tmpdir.dirname - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(dry_run=False) + tc = Manager(get_config_filename('simple.yaml')).sync(dry_run=False) self.assertEqual(28, tc) # try with just one of the zones - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(dry_run=False, eligible_zones=['unit.tests.']) + tc = Manager(get_config_filename('simple.yaml')).sync( + dry_run=False, eligible_zones=['unit.tests.'] + ) self.assertEqual(22, tc) # the subzone, with 2 targets - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(dry_run=False, eligible_zones=['subzone.unit.tests.']) + tc = Manager(get_config_filename('simple.yaml')).sync( + dry_run=False, eligible_zones=['subzone.unit.tests.'] + ) self.assertEqual(6, tc) # and finally the empty zone - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(dry_run=False, eligible_zones=['empty.']) + tc = Manager(get_config_filename('simple.yaml')).sync( + dry_run=False, eligible_zones=['empty.'] + ) self.assertEqual(0, tc) # Again with force - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(dry_run=False, force=True) + tc = Manager(get_config_filename('simple.yaml')).sync( + dry_run=False, force=True + ) self.assertEqual(28, tc) # Again with max_workers = 1 - tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \ - .sync(dry_run=False, force=True) + tc = Manager( + get_config_filename('simple.yaml'), max_workers=1 + ).sync(dry_run=False, force=True) self.assertEqual(28, tc) # Include meta - tc = Manager(get_config_filename('simple.yaml'), max_workers=1, - include_meta=True) \ - .sync(dry_run=False, force=True) + tc = Manager( + get_config_filename('simple.yaml'), + max_workers=1, + include_meta=True, + ).sync(dry_run=False, force=True) self.assertEqual(33, tc) def test_eligible_sources(self): with TemporaryDirectory() as tmpdir: environ['YAML_TMP_DIR'] = tmpdir.dirname # Only allow a target that doesn't exist - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(eligible_sources=['foo']) + tc = Manager(get_config_filename('simple.yaml')).sync( + eligible_sources=['foo'] + ) self.assertEqual(0, tc) def test_eligible_targets(self): with TemporaryDirectory() as tmpdir: environ['YAML_TMP_DIR'] = tmpdir.dirname # Only allow a target that doesn't exist - tc = Manager(get_config_filename('simple.yaml')) \ - .sync(eligible_targets=['foo']) + tc = Manager(get_config_filename('simple.yaml')).sync( + eligible_targets=['foo'] + ) self.assertEqual(0, tc) def test_aliases(self): with TemporaryDirectory() as tmpdir: environ['YAML_TMP_DIR'] = tmpdir.dirname # Alias zones with a valid target. - tc = Manager(get_config_filename('simple-alias-zone.yaml')) \ - .sync() + tc = Manager(get_config_filename('simple-alias-zone.yaml')).sync() self.assertEqual(0, tc) # Alias zone with an invalid target. with self.assertRaises(ManagerException) as ctx: - tc = Manager(get_config_filename('unknown-source-zone.yaml')) \ - .sync() - self.assertEqual('Invalid alias zone alias.tests.: source zone ' - 'does-not-exists.tests. does not exist', - str(ctx.exception)) + tc = Manager( + get_config_filename('unknown-source-zone.yaml') + ).sync() + self.assertEqual( + 'Invalid alias zone alias.tests.: source zone ' + 'does-not-exists.tests. does not exist', + str(ctx.exception), + ) # Alias zone that points to another alias zone. with self.assertRaises(ManagerException) as ctx: - tc = Manager(get_config_filename('alias-zone-loop.yaml')) \ - .sync() - self.assertEqual('Invalid alias zone alias-loop.tests.: source ' - 'zone alias.tests. is an alias zone', - str(ctx.exception)) + tc = Manager(get_config_filename('alias-zone-loop.yaml')).sync() + self.assertEqual( + 'Invalid alias zone alias-loop.tests.: source ' + 'zone alias.tests. is an alias zone', + str(ctx.exception), + ) # Sync an alias without the zone it refers to with self.assertRaises(ManagerException) as ctx: - tc = Manager(get_config_filename('simple-alias-zone.yaml')) \ - .sync(eligible_zones=["alias.tests."]) - self.assertEqual('Zone alias.tests. cannot be sync without zone ' - 'unit.tests. sinced it is aliased', - str(ctx.exception)) + tc = Manager( + get_config_filename('simple-alias-zone.yaml') + ).sync(eligible_zones=["alias.tests."]) + self.assertEqual( + 'Zone alias.tests. cannot be sync without zone ' + 'unit.tests. sinced it is aliased', + str(ctx.exception), + ) def test_compare(self): with TemporaryDirectory() as tmpdir: @@ -223,9 +257,9 @@ class TestManager(TestCase): self.assertEqual(23, len(changes)) # Compound sources with varying support - changes = manager.compare(['in', 'nosshfp'], - ['dump'], - 'unit.tests.') + changes = manager.compare( + ['in', 'nosshfp'], ['dump'], 'unit.tests.' + ) self.assertEqual(22, len(changes)) with self.assertRaises(ManagerException) as ctx: @@ -249,8 +283,9 @@ class TestManager(TestCase): # exception with self.assertRaises(AttributeError) as ctx: at.FOO - self.assertEqual('_AggregateTarget object has no attribute FOO', - str(ctx.exception)) + self.assertEqual( + '_AggregateTarget object has no attribute FOO', str(ctx.exception) + ) self.assertFalse(_AggregateTarget([simple, simple]).SUPPORTS_GEO) self.assertFalse(_AggregateTarget([simple, geo]).SUPPORTS_GEO) @@ -263,15 +298,19 @@ class TestManager(TestCase): self.assertTrue(_AggregateTarget([dynamic, dynamic]).SUPPORTS_DYNAMIC) zone = Zone('unit.tests.', []) - record = Record.new(zone, 'sshfp', { - 'ttl': 60, - 'type': 'SSHFP', - 'value': { - 'algorithm': 1, - 'fingerprint_type': 1, - 'fingerprint': 'abcdefg', + record = Record.new( + zone, + 'sshfp', + { + 'ttl': 60, + 'type': 'SSHFP', + 'value': { + 'algorithm': 1, + 'fingerprint_type': 1, + 'fingerprint': 'abcdefg', + }, }, - }) + ) self.assertTrue(simple.supports(record)) self.assertFalse(nosshfp.supports(record)) self.assertTrue(_AggregateTarget([simple, simple]).supports(record)) @@ -283,8 +322,9 @@ class TestManager(TestCase): manager = Manager(get_config_filename('simple.yaml')) with self.assertRaises(ManagerException) as ctx: - manager.dump('unit.tests.', tmpdir.dirname, False, False, - 'nope') + manager.dump( + 'unit.tests.', tmpdir.dirname, False, False, 'nope' + ) self.assertEqual('Unknown source: nope', str(ctx.exception)) manager.dump('unit.tests.', tmpdir.dirname, False, False, 'in') @@ -292,8 +332,9 @@ class TestManager(TestCase): # make sure this fails with an IOError and not a KeyError when # trying to find sub zones with self.assertRaises(IOError): - manager.dump('unknown.zone.', tmpdir.dirname, False, False, - 'in') + manager.dump( + 'unknown.zone.', tmpdir.dirname, False, False, 'in' + ) def test_dump_empty(self): with TemporaryDirectory() as tmpdir: @@ -312,8 +353,7 @@ class TestManager(TestCase): manager = Manager(get_config_filename('simple-split.yaml')) with self.assertRaises(ManagerException) as ctx: - manager.dump('unit.tests.', tmpdir.dirname, False, True, - 'nope') + manager.dump('unit.tests.', tmpdir.dirname, False, True, 'nope') self.assertEqual('Unknown source: nope', str(ctx.exception)) manager.dump('unit.tests.', tmpdir.dirname, False, True, 'in') @@ -321,41 +361,46 @@ class TestManager(TestCase): # make sure this fails with an OSError and not a KeyError when # trying to find sub zones with self.assertRaises(OSError): - manager.dump('unknown.zone.', tmpdir.dirname, False, True, - 'in') + manager.dump('unknown.zone.', tmpdir.dirname, False, True, 'in') def test_validate_configs(self): Manager(get_config_filename('simple-validate.yaml')).validate_configs() with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('missing-sources.yaml')) \ - .validate_configs() + Manager( + get_config_filename('missing-sources.yaml') + ).validate_configs() self.assertTrue('missing sources' in str(ctx.exception)) with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('unknown-provider.yaml')) \ - .validate_configs() + Manager( + get_config_filename('unknown-provider.yaml') + ).validate_configs() self.assertTrue('unknown source' in str(ctx.exception)) # Alias zone using an invalid source zone. with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('unknown-source-zone.yaml')) \ - .validate_configs() + Manager( + get_config_filename('unknown-source-zone.yaml') + ).validate_configs() self.assertTrue('does not exist' in str(ctx.exception)) # Alias zone that points to another alias zone. with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('alias-zone-loop.yaml')) \ - .validate_configs() + Manager( + get_config_filename('alias-zone-loop.yaml') + ).validate_configs() self.assertTrue('is an alias zone' in str(ctx.exception)) # Valid config file using an alias zone. - Manager(get_config_filename('simple-alias-zone.yaml')) \ - .validate_configs() + Manager( + get_config_filename('simple-alias-zone.yaml') + ).validate_configs() with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('unknown-processor.yaml')) \ - .validate_configs() + Manager( + get_config_filename('unknown-processor.yaml') + ).validate_configs() self.assertTrue('unknown processor' in str(ctx.exception)) def test_get_zone(self): @@ -366,8 +411,9 @@ class TestManager(TestCase): self.assertTrue('missing ending dot' in str(ctx.exception)) with self.assertRaises(ManagerException) as ctx: - Manager(get_config_filename('simple.yaml')) \ - .get_zone('unknown-zone.tests.') + Manager(get_config_filename('simple.yaml')).get_zone( + 'unknown-zone.tests.' + ) self.assertTrue('Unknown zone name' in str(ctx.exception)) def test_populate_lenient_fallback(self): @@ -377,7 +423,6 @@ class TestManager(TestCase): manager = Manager(get_config_filename('simple.yaml')) class NoLenient(SimpleProvider): - def populate(self, zone): pass @@ -385,14 +430,12 @@ class TestManager(TestCase): manager._populate_and_plan('unit.tests.', [], [NoLenient()], []) class OtherType(SimpleProvider): - def populate(self, zone, lenient=False): raise TypeError('something else') # This will blow up, we don't fallback for source with self.assertRaises(TypeError) as ctx: - manager._populate_and_plan('unit.tests.', [], [OtherType()], - []) + manager._populate_and_plan('unit.tests.', [], [OtherType()], []) self.assertEqual('something else', str(ctx.exception)) def test_plan_processors_fallback(self): @@ -402,23 +445,19 @@ class TestManager(TestCase): manager = Manager(get_config_filename('simple.yaml')) class NoProcessors(SimpleProvider): - def plan(self, zone): pass # This should be ok, we'll fall back to not passing it - manager._populate_and_plan('unit.tests.', [], [], - [NoProcessors()]) + manager._populate_and_plan('unit.tests.', [], [], [NoProcessors()]) class OtherType(SimpleProvider): - def plan(self, zone, processors): raise TypeError('something else') # This will blow up, we don't fallback for source with self.assertRaises(TypeError) as ctx: - manager._populate_and_plan('unit.tests.', [], [], - [OtherType()]) + manager._populate_and_plan('unit.tests.', [], [], [OtherType()]) self.assertEqual('something else', str(ctx.exception)) @patch('octodns.manager.Manager._get_named_class') @@ -429,8 +468,9 @@ class TestManager(TestCase): mock.return_value = (plan_output_class_mock, 'ignored', 'ignored') fh_mock = MagicMock() - Manager(get_config_filename('plan-output-filehandle.yaml') - ).sync(plan_output_fh=fh_mock) + Manager(get_config_filename('plan-output-filehandle.yaml')).sync( + plan_output_fh=fh_mock + ) # Since we only care about the fh kwarg, and different _PlanOutputs are # are free to require arbitrary kwargs anyway, we concern ourselves @@ -449,18 +489,22 @@ class TestManager(TestCase): with self.assertRaises(ManagerException) as ctx: # This zone specifies a non-existent processor manager.sync(['bad.unit.tests.']) - self.assertTrue('Zone bad.unit.tests., unknown processor: ' - 'doesnt-exist' in str(ctx.exception)) + self.assertTrue( + 'Zone bad.unit.tests., unknown processor: ' + 'doesnt-exist' in str(ctx.exception) + ) with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('processors-missing-class.yaml')) - self.assertTrue('Processor no-class is missing class' in - str(ctx.exception)) + self.assertTrue( + 'Processor no-class is missing class' in str(ctx.exception) + ) with self.assertRaises(ManagerException) as ctx: Manager(get_config_filename('processors-wants-config.yaml')) - self.assertTrue('Incorrect processor config for wants-config' in - str(ctx.exception)) + self.assertTrue( + 'Incorrect processor config for wants-config' in str(ctx.exception) + ) def test_processors(self): manager = Manager(get_config_filename('simple.yaml')) @@ -468,23 +512,21 @@ class TestManager(TestCase): targets = [PlannableProvider('prov')] zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 30, - 'type': 'A', - 'value': '1.2.3.4', - }) + record = Record.new( + zone, 'a', {'ttl': 30, 'type': 'A', 'value': '1.2.3.4'} + ) # muck with sources class MockProcessor(BaseProcessor): - def process_source_zone(self, zone, sources): zone = zone.copy() zone.add_record(record) return zone mock = MockProcessor('mock') - plans, zone = manager._populate_and_plan('unit.tests.', [mock], [], - targets) + plans, zone = manager._populate_and_plan( + 'unit.tests.', [mock], [], targets + ) # Our mock was called and added the record self.assertEqual(record, list(zone.records)[0]) # We got a create for the thing added to the expected state (source) @@ -492,15 +534,15 @@ class TestManager(TestCase): # muck with targets class MockProcessor(BaseProcessor): - def process_target_zone(self, zone, target): zone = zone.copy() zone.add_record(record) return zone mock = MockProcessor('mock') - plans, zone = manager._populate_and_plan('unit.tests.', [mock], [], - targets) + plans, zone = manager._populate_and_plan( + 'unit.tests.', [mock], [], targets + ) # No record added since it's target this time self.assertFalse(zone.records) # We got a delete for the thing added to the existing state (target) @@ -508,7 +550,6 @@ class TestManager(TestCase): # muck with plans class MockProcessor(BaseProcessor): - def process_target_zone(self, zone, target): zone = zone.copy() zone.add_record(record) @@ -519,8 +560,9 @@ class TestManager(TestCase): plans.changes.pop(0) mock = MockProcessor('mock') - plans, zone = manager._populate_and_plan('unit.tests.', [mock], [], - targets) + plans, zone = manager._populate_and_plan( + 'unit.tests.', [mock], [], targets + ) # We planned a delete again, but this time removed it from the plan, so # no plans self.assertFalse(plans) @@ -534,23 +576,28 @@ class TestManager(TestCase): dummy_module = DummyModule() # use importlib.metadata.version - self.assertTrue(__VERSION__, - manager._try_version('octodns', - module=dummy_module, - version='1.2.3')) + self.assertTrue( + __VERSION__, + manager._try_version( + 'octodns', module=dummy_module, version='1.2.3' + ), + ) # use module - self.assertTrue(manager._try_version('doesnt-exist', - module=dummy_module)) + self.assertTrue( + manager._try_version('doesnt-exist', module=dummy_module) + ) # fall back to version, preferred over module - self.assertEqual('1.2.3', manager._try_version('doesnt-exist', - module=dummy_module, - version='1.2.3', )) + self.assertEqual( + '1.2.3', + manager._try_version( + 'doesnt-exist', module=dummy_module, version='1.2.3' + ), + ) class TestMainThreadExecutor(TestCase): - def test_success(self): mte = MainThreadExecutor() diff --git a/tests/test_octodns_plan.py b/tests/test_octodns_plan.py index d302ceb..3b614c7 100644 --- a/tests/test_octodns_plan.py +++ b/tests/test_octodns_plan.py @@ -2,15 +2,25 @@ # # -from __future__ import absolute_import, division, print_function, \ - unicode_literals +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) from io import StringIO from logging import getLogger from unittest import TestCase -from octodns.provider.plan import Plan, PlanHtml, PlanLogger, PlanMarkdown, \ - RootNsChange, TooMuchChange +from octodns.provider.plan import ( + Plan, + PlanHtml, + PlanLogger, + PlanMarkdown, + RootNsChange, + TooMuchChange, +) from octodns.record import Create, Delete, Record, Update from octodns.zone import Zone @@ -19,32 +29,41 @@ from helpers import SimpleProvider simple = SimpleProvider() zone = Zone('unit.tests.', []) -existing = Record.new(zone, 'a', { - 'ttl': 300, - 'type': 'A', - # This matches the zone data above, one to swap, one to leave - 'values': ['1.1.1.1', '2.2.2.2'], -}) -new = Record.new(zone, 'a', { - 'geo': { - 'AF': ['5.5.5.5'], - 'NA-US': ['6.6.6.6'] +existing = Record.new( + zone, + 'a', + { + 'ttl': 300, + 'type': 'A', + # This matches the zone data above, one to swap, one to leave + 'values': ['1.1.1.1', '2.2.2.2'], }, - 'ttl': 300, - 'type': 'A', - # This leaves one, swaps ones, and adds one - 'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'], -}, simple) -create = Create(Record.new(zone, 'b', { - 'ttl': 60, - 'type': 'CNAME', - 'value': 'foo.unit.tests.' -}, simple)) -create2 = Create(Record.new(zone, 'c', { - 'ttl': 60, - 'type': 'CNAME', - 'value': 'foo.unit.tests.' -})) +) +new = Record.new( + zone, + 'a', + { + 'geo': {'AF': ['5.5.5.5'], 'NA-US': ['6.6.6.6']}, + 'ttl': 300, + 'type': 'A', + # This leaves one, swaps ones, and adds one + 'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'], + }, + simple, +) +create = Create( + Record.new( + zone, + 'b', + {'ttl': 60, 'type': 'CNAME', 'value': 'foo.unit.tests.'}, + simple, + ) +) +create2 = Create( + Record.new( + zone, 'c', {'ttl': 60, 'type': 'CNAME', 'value': 'foo.unit.tests.'} + ) +) update = Update(existing, new) delete = Delete(new) changes = [create, create2, delete, update] @@ -55,23 +74,18 @@ plans = [ class TestPlanSortsChanges(TestCase): - def test_plan_sorts_changes_pass_to_it(self): # we aren't worried about the details of the sorting, that's tested in # test_octodns_record's TestChanges. We just want to make sure that the # changes are sorted at all. zone = Zone('unit.tests.', []) - record_a_1 = Record.new(zone, '1', { - 'type': 'A', - 'ttl': 30, - 'value': '1.2.3.4', - }) + record_a_1 = Record.new( + zone, '1', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'} + ) create_a_1 = Create(record_a_1) - record_a_2 = Record.new(zone, '2', { - 'type': 'A', - 'ttl': 30, - 'value': '1.2.3.4', - }) + record_a_2 = Record.new( + zone, '2', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'} + ) create_a_2 = Create(record_a_2) # passed in reverse of expected order @@ -80,16 +94,13 @@ class TestPlanSortsChanges(TestCase): class TestPlanLogger(TestCase): - def test_invalid_level(self): with self.assertRaises(Exception) as ctx: PlanLogger('invalid', 'not-a-level') self.assertEqual('Unsupported level: not-a-level', str(ctx.exception)) def test_create(self): - class MockLogger(object): - def __init__(self): self.out = StringIO() @@ -99,8 +110,10 @@ class TestPlanLogger(TestCase): log = MockLogger() PlanLogger('logger').run(log, plans) out = log.out.getvalue() - self.assertTrue('Summary: Creates=2, Updates=1, ' - 'Deletes=1, Existing Records=0' in out) + self.assertTrue( + 'Summary: Creates=2, Updates=1, ' + 'Deletes=1, Existing Records=0' in out + ) class TestPlanHtml(TestCase): @@ -115,8 +128,10 @@ class TestPlanHtml(TestCase): out = StringIO() PlanHtml('html').run(plans, fh=out) out = out.getvalue() - self.assertTrue(' Summary: Creates=2, Updates=1, ' - 'Deletes=1, Existing Records=0Summary: Creates=2, Updates=1, ' + 'Deletes=1, Existing Records=0