From f2a6f870b40d317b95dbd3e12e8d6ddb0324f33b Mon Sep 17 00:00:00 2001 From: Jonathan Leroy Date: Tue, 20 Oct 2020 22:18:48 +0200 Subject: [PATCH] Make each alias zone reference its target zone instead of listing all aliases zones in the target zone configuration --- octodns/manager.py | 39 ++++++++++++++++++++++----- octodns/provider/yaml.py | 5 ++-- octodns/zone.py | 4 ++- tests/config/simple-alias-zone.yaml | 19 +++++++++++++ tests/config/unknown-source-zone.yaml | 13 +++++++++ tests/test_octodns_manager.py | 19 +++++++++++-- 6 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 tests/config/simple-alias-zone.yaml create mode 100644 tests/config/unknown-source-zone.yaml diff --git a/octodns/manager.py b/octodns/manager.py index 288645f..613be29 100644 --- a/octodns/manager.py +++ b/octodns/manager.py @@ -121,6 +121,23 @@ class Manager(object): raise ManagerException('Incorrect provider config for {}' .format(provider_name)) + for zone_name, zone_config in self.config['zones'].copy().items(): + if 'alias' in zone_config: + source_zone = zone_config['alias'] + # Check that the source zone is defined. + if source_zone not in self.config['zones']: + self.log.exception('Invalid alias zone') + raise ManagerException('Invalid alias zone {}: ' + 'source zone {} does not exist' + .format(zone_name, source_zone)) + self.config['zones'][zone_name] = \ + self.config['zones'][source_zone] + self.config['zones'][zone_name]['is_alias'] = True + self.config['zones'][zone_name]['file'] = source_zone + else: + self.config['zones'][zone_name]['is_alias'] = False + self.config['zones'][zone_name]['file'] = zone_name + zone_tree = {} # sort by reversed strings so that parent zones always come first for name in sorted(self.config['zones'].keys(), key=lambda s: s[::-1]): @@ -222,12 +239,14 @@ class Manager(object): self.log.debug('configured_sub_zones: subs=%s', sub_zone_names) return set(sub_zone_names) - def _populate_and_plan(self, zone_name, sources, targets, lenient=False): + def _populate_and_plan(self, zone_name, file, is_alias, sources, targets, + lenient=False): - self.log.debug('sync: populating, zone=%s, lenient=%s', - zone_name, lenient) + self.log.debug('sync: populating, zone=%s, file=%s, is_alias=%s, ' + 'lenient=%s', zone_name, file, is_alias, lenient) zone = Zone(zone_name, - sub_zones=self.configured_sub_zones(zone_name)) + sub_zones=self.configured_sub_zones(zone_name), file=file, + is_alias=is_alias) for source in sources: try: source.populate(zone, lenient=lenient) @@ -268,6 +287,8 @@ class Manager(object): futures = [] for zone_name, config in zones: self.log.info('sync: zone=%s', zone_name) + file = config.get('file') + is_alias = config.get('is_alias') lenient = config.get('lenient', False) try: sources = config['sources'] @@ -324,8 +345,9 @@ class Manager(object): .format(zone_name, target)) futures.append(self._executor.submit(self._populate_and_plan, - zone_name, sources, - targets, lenient=lenient)) + zone_name, file, is_alias, + sources, targets, + lenient=lenient)) # Wait on all results and unpack/flatten them in to a list of target & # plan pairs. @@ -419,7 +441,10 @@ class Manager(object): def validate_configs(self): for zone_name, config in self.config['zones'].items(): - zone = Zone(zone_name, self.configured_sub_zones(zone_name)) + file = config.get('file', False) + is_alias = config.get('is_alias', False) + zone = Zone(zone_name, self.configured_sub_zones(zone_name), + file, is_alias) try: sources = config['sources'] diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 10add5a..e486982 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -139,7 +139,8 @@ class YamlProvider(BaseProvider): filename) def populate(self, zone, target=False, lenient=False): - self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, + self.log.debug('populate: name=%s, file=%s, is_alias:%s, target=%s, ' + 'lenient=%s', zone.name, zone.file, zone.is_alias, target, lenient) if target: @@ -148,7 +149,7 @@ class YamlProvider(BaseProvider): return False before = len(zone.records) - filename = join(self.directory, '{}yaml'.format(zone.name)) + filename = join(self.directory, '{}yaml'.format(zone.file)) self._populate_from_file(filename, zone, lenient) self.log.info('populate: found %s records, exists=False', diff --git a/octodns/zone.py b/octodns/zone.py index 5f099ac..0a78f72 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -35,13 +35,15 @@ def _is_eligible(record): class Zone(object): log = getLogger('Zone') - def __init__(self, name, sub_zones): + def __init__(self, name, sub_zones, file=None, is_alias=False): if not name[-1] == '.': raise Exception('Invalid zone name {}, missing ending dot' .format(name)) # Force everything to lowercase just to be safe self.name = text_type(name).lower() if name else name self.sub_zones = sub_zones + self.file = text_type(file if file else name).lower() + self.is_alias = is_alias # We're grouping by node, it allows us to efficiently search for # duplicates and detect when CNAMEs co-exist with other records self._records = defaultdict(set) diff --git a/tests/config/simple-alias-zone.yaml b/tests/config/simple-alias-zone.yaml new file mode 100644 index 0000000..32154d5 --- /dev/null +++ b/tests/config/simple-alias-zone.yaml @@ -0,0 +1,19 @@ +manager: + max_workers: 2 +providers: + in: + class: octodns.provider.yaml.YamlProvider + directory: tests/config + dump: + class: octodns.provider.yaml.YamlProvider + directory: env/YAML_TMP_DIR +zones: + unit.tests.: + sources: + - in + targets: + - dump + + alias.tests.: + alias: unit.tests. + diff --git a/tests/config/unknown-source-zone.yaml b/tests/config/unknown-source-zone.yaml new file mode 100644 index 0000000..313853e --- /dev/null +++ b/tests/config/unknown-source-zone.yaml @@ -0,0 +1,13 @@ +manager: + max_workers: 2 +providers: + in: + class: octodns.provider.yaml.YamlProvider + directory: tests/config + dump: + class: octodns.provider.yaml.YamlProvider + directory: env/YAML_TMP_DIR +zones: + unit.tests.: + alias: unit-source.tests. + diff --git a/tests/test_octodns_manager.py b/tests/test_octodns_manager.py index 9956790..1b8752e 100644 --- a/tests/test_octodns_manager.py +++ b/tests/test_octodns_manager.py @@ -298,7 +298,8 @@ class TestManager(TestCase): pass # This should be ok, we'll fall back to not passing it - manager._populate_and_plan('unit.tests.', [NoLenient()], []) + manager._populate_and_plan('unit.tests.', None, False, + [NoLenient()], []) class NoZone(SimpleProvider): @@ -307,7 +308,21 @@ class TestManager(TestCase): # This will blow up, we don't fallback for source with self.assertRaises(TypeError): - manager._populate_and_plan('unit.tests.', [NoZone()], []) + manager._populate_and_plan('unit.tests.', None, False, + [NoZone()], []) + + def test_alias_zones(self): + with TemporaryDirectory() as tmpdir: + environ['YAML_TMP_DIR'] = tmpdir.dirname + + Manager(get_config_filename('simple-alias-zone.yaml')) \ + .validate_configs() + + with self.assertRaises(ManagerException) as ctx: + Manager(get_config_filename('unknown-source-zone.yaml')) \ + .validate_configs() + self.assertTrue('Invalid alias zone' in + text_type(ctx.exception)) class TestMainThreadExecutor(TestCase):