mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Merge branch 'master' into idna
This commit is contained in:
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: ross
|
|
||||||
@@ -162,23 +162,21 @@ class Manager(object):
|
|||||||
processor_name)
|
processor_name)
|
||||||
|
|
||||||
zone_tree = {}
|
zone_tree = {}
|
||||||
# sort by reversed strings so that parent zones always come first
|
# Sort so we iterate on the deepest nodes first, ensuring if a parent
|
||||||
for name in sorted(self.config['zones'].keys(), key=lambda s: s[::-1]):
|
# zone exists it will be seen after the subzone, thus we can easily
|
||||||
# ignore trailing dots, and reverse
|
# reparent children to their parent zone from the tree root.
|
||||||
pieces = name[:-1].split('.')[::-1]
|
for name in sorted(self.config['zones'].keys(),
|
||||||
# where starts out at the top
|
key=lambda s: 0 - s.count('.')):
|
||||||
where = zone_tree
|
# Trim the trailing dot from FQDN
|
||||||
# for all the pieces
|
name = name[:-1]
|
||||||
for piece in pieces:
|
this = {}
|
||||||
try:
|
for sz in [k for k in zone_tree.keys() if k.endswith(name)]:
|
||||||
where = where[piece]
|
# Found a zone in tree root that is our child, slice the
|
||||||
# our current piece already exists, just point where at
|
# name and move its tree under ours.
|
||||||
# it's value
|
this[sz[:-(len(name) + 1)]] = zone_tree.pop(sz)
|
||||||
except KeyError:
|
# Add to tree root where it will be reparented as we iterate up
|
||||||
# our current piece doesn't exist, create it
|
# the tree.
|
||||||
where[piece] = {}
|
zone_tree[name] = this
|
||||||
# and then point where at it's newly created value
|
|
||||||
where = where[piece]
|
|
||||||
self.zone_tree = zone_tree
|
self.zone_tree = zone_tree
|
||||||
|
|
||||||
self.plan_outputs = {}
|
self.plan_outputs = {}
|
||||||
@@ -274,21 +272,20 @@ class Manager(object):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def configured_sub_zones(self, zone_name):
|
def configured_sub_zones(self, zone_name):
|
||||||
# Reversed pieces of the zone name
|
name = zone_name[:-1]
|
||||||
pieces = zone_name[:-1].split('.')[::-1]
|
|
||||||
# Point where at the root of the tree
|
|
||||||
where = self.zone_tree
|
where = self.zone_tree
|
||||||
# Until we've hit the bottom of this zone
|
while True:
|
||||||
try:
|
# Find parent if it exists
|
||||||
while pieces:
|
parent = next((k for k in where if name.endswith(k)), None)
|
||||||
# Point where at the value of our current piece
|
if not parent:
|
||||||
where = where[pieces.pop(0)]
|
# The zone_name in the tree has been reached, stop searching.
|
||||||
except KeyError:
|
break
|
||||||
self.log.debug('configured_sub_zones: unknown zone, %s, no subs',
|
# Move down the tree and slice name to get the remainder for the
|
||||||
zone_name)
|
# next round of the search.
|
||||||
return set()
|
where = where[parent]
|
||||||
# We're not pointed at the dict for our name, the keys of which will be
|
name = name[:-(len(parent) + 1)]
|
||||||
# any subzones
|
# `where` is now pointing at the dictionary of children for zone_name
|
||||||
|
# in the tree
|
||||||
sub_zone_names = where.keys()
|
sub_zone_names = where.keys()
|
||||||
self.log.debug('configured_sub_zones: subs=%s', sub_zone_names)
|
self.log.debug('configured_sub_zones: subs=%s', sub_zone_names)
|
||||||
return set(sub_zone_names)
|
return set(sub_zone_names)
|
||||||
|
|||||||
@@ -68,10 +68,9 @@ class Zone(object):
|
|||||||
self.hydrate()
|
self.hydrate()
|
||||||
|
|
||||||
name = record.name
|
name = record.name
|
||||||
last = name.split('.')[-1]
|
|
||||||
|
|
||||||
if not lenient and last in self.sub_zones:
|
if not lenient and any((name.endswith(sz) for sz in self.sub_zones)):
|
||||||
if name != last:
|
if name not in self.sub_zones:
|
||||||
# it's a record for something under a sub-zone
|
# it's a record for something under a sub-zone
|
||||||
raise SubzoneRecordException(f'Record {record.fqdn} is under '
|
raise SubzoneRecordException(f'Record {record.fqdn} is under '
|
||||||
'a managed subzone')
|
'a managed subzone')
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ zones:
|
|||||||
targets:
|
targets:
|
||||||
- dump
|
- dump
|
||||||
- dump2
|
- dump2
|
||||||
|
sub.txt.unit.tests.:
|
||||||
|
sources:
|
||||||
|
- in
|
||||||
|
targets:
|
||||||
|
- dump
|
||||||
empty.:
|
empty.:
|
||||||
sources:
|
sources:
|
||||||
- in
|
- in
|
||||||
|
|||||||
1
tests/config/sub.txt.unit.tests.yaml
Normal file
1
tests/config/sub.txt.unit.tests.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
---
|
||||||
@@ -162,6 +162,16 @@ sub:
|
|||||||
values:
|
values:
|
||||||
- 6.2.3.4.
|
- 6.2.3.4.
|
||||||
- 7.2.3.4.
|
- 7.2.3.4.
|
||||||
|
sub.txt:
|
||||||
|
type: 'NS'
|
||||||
|
values:
|
||||||
|
- ns1.test.
|
||||||
|
- ns2.test.
|
||||||
|
subzone:
|
||||||
|
type: 'NS'
|
||||||
|
values:
|
||||||
|
- 192.0.2.1.
|
||||||
|
- 192.0.2.8.
|
||||||
txt:
|
txt:
|
||||||
ttl: 600
|
ttl: 600
|
||||||
type: TXT
|
type: TXT
|
||||||
|
|||||||
@@ -121,12 +121,12 @@ class TestManager(TestCase):
|
|||||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||||
tc = Manager(get_config_filename('simple.yaml')) \
|
tc = Manager(get_config_filename('simple.yaml')) \
|
||||||
.sync(dry_run=False)
|
.sync(dry_run=False)
|
||||||
self.assertEqual(26, tc)
|
self.assertEqual(28, tc)
|
||||||
|
|
||||||
# try with just one of the zones
|
# try with just one of the zones
|
||||||
tc = Manager(get_config_filename('simple.yaml')) \
|
tc = Manager(get_config_filename('simple.yaml')) \
|
||||||
.sync(dry_run=False, eligible_zones=['unit.tests.'])
|
.sync(dry_run=False, eligible_zones=['unit.tests.'])
|
||||||
self.assertEqual(20, tc)
|
self.assertEqual(22, tc)
|
||||||
|
|
||||||
# the subzone, with 2 targets
|
# the subzone, with 2 targets
|
||||||
tc = Manager(get_config_filename('simple.yaml')) \
|
tc = Manager(get_config_filename('simple.yaml')) \
|
||||||
@@ -141,18 +141,18 @@ class TestManager(TestCase):
|
|||||||
# Again with force
|
# Again with force
|
||||||
tc = Manager(get_config_filename('simple.yaml')) \
|
tc = Manager(get_config_filename('simple.yaml')) \
|
||||||
.sync(dry_run=False, force=True)
|
.sync(dry_run=False, force=True)
|
||||||
self.assertEqual(26, tc)
|
self.assertEqual(28, tc)
|
||||||
|
|
||||||
# Again with max_workers = 1
|
# Again with max_workers = 1
|
||||||
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
|
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
|
||||||
.sync(dry_run=False, force=True)
|
.sync(dry_run=False, force=True)
|
||||||
self.assertEqual(26, tc)
|
self.assertEqual(28, tc)
|
||||||
|
|
||||||
# Include meta
|
# Include meta
|
||||||
tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
|
tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
|
||||||
include_meta=True) \
|
include_meta=True) \
|
||||||
.sync(dry_run=False, force=True)
|
.sync(dry_run=False, force=True)
|
||||||
self.assertEqual(30, tc)
|
self.assertEqual(33, tc)
|
||||||
|
|
||||||
def test_eligible_sources(self):
|
def test_eligible_sources(self):
|
||||||
with TemporaryDirectory() as tmpdir:
|
with TemporaryDirectory() as tmpdir:
|
||||||
@@ -220,13 +220,13 @@ class TestManager(TestCase):
|
|||||||
# compare doesn't use _process_desired_zone and thus doesn't filter
|
# compare doesn't use _process_desired_zone and thus doesn't filter
|
||||||
# out root NS records, that seems fine/desirable
|
# out root NS records, that seems fine/desirable
|
||||||
changes = manager.compare(['in'], ['dump'], 'unit.tests.')
|
changes = manager.compare(['in'], ['dump'], 'unit.tests.')
|
||||||
self.assertEqual(21, len(changes))
|
self.assertEqual(23, len(changes))
|
||||||
|
|
||||||
# Compound sources with varying support
|
# Compound sources with varying support
|
||||||
changes = manager.compare(['in', 'nosshfp'],
|
changes = manager.compare(['in', 'nosshfp'],
|
||||||
['dump'],
|
['dump'],
|
||||||
'unit.tests.')
|
'unit.tests.')
|
||||||
self.assertEqual(20, len(changes))
|
self.assertEqual(22, len(changes))
|
||||||
|
|
||||||
with self.assertRaises(ManagerException) as ctx:
|
with self.assertRaises(ManagerException) as ctx:
|
||||||
manager.compare(['nope'], ['dump'], 'unit.tests.')
|
manager.compare(['nope'], ['dump'], 'unit.tests.')
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class TestYamlProvider(TestCase):
|
|||||||
|
|
||||||
# without it we see everything
|
# without it we see everything
|
||||||
source.populate(zone)
|
source.populate(zone)
|
||||||
self.assertEqual(23, len(zone.records))
|
self.assertEqual(25, len(zone.records))
|
||||||
|
|
||||||
source.populate(dynamic_zone)
|
source.populate(dynamic_zone)
|
||||||
self.assertEqual(6, len(dynamic_zone.records))
|
self.assertEqual(6, len(dynamic_zone.records))
|
||||||
@@ -57,12 +57,12 @@ class TestYamlProvider(TestCase):
|
|||||||
|
|
||||||
# We add everything
|
# We add everything
|
||||||
plan = target.plan(zone)
|
plan = target.plan(zone)
|
||||||
self.assertEqual(20, len([c for c in plan.changes
|
self.assertEqual(22, len([c for c in plan.changes
|
||||||
if isinstance(c, Create)]))
|
if isinstance(c, Create)]))
|
||||||
self.assertFalse(isfile(yaml_file))
|
self.assertFalse(isfile(yaml_file))
|
||||||
|
|
||||||
# Now actually do it
|
# Now actually do it
|
||||||
self.assertEqual(20, target.apply(plan))
|
self.assertEqual(22, target.apply(plan))
|
||||||
self.assertTrue(isfile(yaml_file))
|
self.assertTrue(isfile(yaml_file))
|
||||||
|
|
||||||
# Dynamic plan
|
# Dynamic plan
|
||||||
@@ -90,7 +90,7 @@ class TestYamlProvider(TestCase):
|
|||||||
|
|
||||||
# A 2nd sync should still create everything
|
# A 2nd sync should still create everything
|
||||||
plan = target.plan(zone)
|
plan = target.plan(zone)
|
||||||
self.assertEqual(20, len([c for c in plan.changes
|
self.assertEqual(22, len([c for c in plan.changes
|
||||||
if isinstance(c, Create)]))
|
if isinstance(c, Create)]))
|
||||||
|
|
||||||
with open(yaml_file) as fh:
|
with open(yaml_file) as fh:
|
||||||
@@ -111,6 +111,8 @@ class TestYamlProvider(TestCase):
|
|||||||
self.assertTrue('values' in data.pop('txt'))
|
self.assertTrue('values' in data.pop('txt'))
|
||||||
self.assertTrue('values' in data.pop('loc'))
|
self.assertTrue('values' in data.pop('loc'))
|
||||||
self.assertTrue('values' in data.pop('urlfwd'))
|
self.assertTrue('values' in data.pop('urlfwd'))
|
||||||
|
self.assertTrue('values' in data.pop('sub.txt'))
|
||||||
|
self.assertTrue('values' in data.pop('subzone'))
|
||||||
# these are stored as singular 'value'
|
# these are stored as singular 'value'
|
||||||
self.assertTrue('value' in data.pop('_imap._tcp'))
|
self.assertTrue('value' in data.pop('_imap._tcp'))
|
||||||
self.assertTrue('value' in data.pop('_pop3._tcp'))
|
self.assertTrue('value' in data.pop('_pop3._tcp'))
|
||||||
|
|||||||
Reference in New Issue
Block a user