From 5592f5da96a297f6563c82ea35d74f790f6dedd3 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Thu, 23 Jun 2022 20:31:49 +1000 Subject: [PATCH] Support dotted subdomains for subzones Currently if there are two zones configured; - example.com. - delegated.subdomain.example.com. When an NS record is created in example.com.yaml as such: delegated.subdomain: type: NS values: - ns1.example.org. The NS record for delegated.subdomain.example.com cannot be created as it throws an exception: octodns.zone.SubzoneRecordException: Record delegated.subdomain.example.com is under a managed subzone Additionally, all records other than NS are rejected for subdomain.example.com.. This is caused by zone_tree being the result of all zones split on '.' and being added to the tree, even if a zone does not exist at that point. To support records where a subzone is dotted, the the map is built such that each node represents the subdomain of the closest subzone. Before: {"com", {"example": {"subdomain": {"delegated": {}}}}} After: {"example.com": {"delegated.subdomain": {}}} Fixes: #378 --- octodns/manager.py | 50 ++++++++++++++++++---------------------------- octodns/zone.py | 6 +++--- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/octodns/manager.py b/octodns/manager.py index e640a8d..27b384d 100644 --- a/octodns/manager.py +++ b/octodns/manager.py @@ -162,23 +162,18 @@ class Manager(object): processor_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]): - # ignore trailing dots, and reverse - pieces = name[:-1].split('.')[::-1] - # where starts out at the top - where = zone_tree - # for all the pieces - for piece in pieces: - try: - where = where[piece] - # our current piece already exists, just point where at - # it's value - except KeyError: - # our current piece doesn't exist, create it - where[piece] = {} - # and then point where at it's newly created value - where = where[piece] + # 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('.')): + name = name[:-1] + this = {} + for sz in filter( + lambda k: k.endswith(name), set(zone_tree.keys()) + ): + this[sz[:-(len(name) + 1)]] = zone_tree.pop(sz) + zone_tree[name] = this self.zone_tree = zone_tree self.plan_outputs = {} @@ -274,21 +269,14 @@ class Manager(object): return kwargs def configured_sub_zones(self, zone_name): - # Reversed pieces of the zone name - pieces = zone_name[:-1].split('.')[::-1] - # Point where at the root of the tree + name = zone_name[:-1] where = self.zone_tree - # Until we've hit the bottom of this zone - try: - while pieces: - # Point where at the value of our current piece - where = where[pieces.pop(0)] - except KeyError: - self.log.debug('configured_sub_zones: unknown zone, %s, no subs', - zone_name) - return set() - # We're not pointed at the dict for our name, the keys of which will be - # any subzones + while True: + parent = next(filter(lambda k: name.endswith(k), where), None) + if not parent: + break + where = where[parent] + name = name[:-(len(parent) + 1)] sub_zone_names = where.keys() self.log.debug('configured_sub_zones: subs=%s', sub_zone_names) return set(sub_zone_names) diff --git a/octodns/zone.py b/octodns/zone.py index 4cd5e91..41cd6ec 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -68,10 +68,10 @@ class Zone(object): self.hydrate() name = record.name - last = name.split('.')[-1] - if not lenient and last in self.sub_zones: - if name != last: + if not lenient and any(map(lambda sz: name.endswith(sz), + 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')