Browse Source

Backwards compatible _preprocess_zones that also supports regexes, needs more testing

pull/1304/head
Ross McFarland 3 months ago
parent
commit
5844a9d4d6
No known key found for this signature in database GPG Key ID: 943B179E15D3B22A
2 changed files with 66 additions and 13 deletions
  1. +33
    -13
      octodns/manager.py
  2. +33
    -0
      tests/test_octodns_manager.py

+ 33
- 13
octodns/manager.py View File

@ -10,6 +10,7 @@ from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as module_version from importlib.metadata import version as module_version
from json import dumps from json import dumps
from logging import getLogger from logging import getLogger
from re import compile as re_compile
from sys import stdout from sys import stdout
from . import __version__ from . import __version__
@ -591,30 +592,49 @@ class Manager(object):
the call and the zones returned from this function should be used the call and the zones returned from this function should be used
instead. instead.
''' '''
for name, config in list(zones.items()):
if not name.startswith('*'):
# sorting longest first with the assumption that'll longer wildcards or
# regexes will be more specific, but mostly it's just to make the
# behavior consistent
for name, config in sorted(
zones.items(), key=lambda d: len(d[0]), reverse=True
):
if name[0] != '*' and name[-1] != '$':
# this isn't a dynamic zone config, move along
continue continue
# we've found a dynamic config element
# find its sources
# it's dynamic, get a list of zone names from the configured sources
found_sources = sources or self._get_sources( found_sources = sources or self._get_sources(
name, config, eligible_sources name, config, eligible_sources
) )
self.log.info('sync: dynamic zone=%s, sources=%s', name, sources) self.log.info('sync: dynamic zone=%s, sources=%s', name, sources)
sourced_zones = set()
for source in found_sources: for source in found_sources:
if not hasattr(source, 'list_zones'): if not hasattr(source, 'list_zones'):
raise ManagerException( raise ManagerException(
f'dynamic zone={name} includes a source, {source.id}, that does not support `list_zones`' f'dynamic zone={name} includes a source, {source.id}, that does not support `list_zones`'
) )
for zone_name in source.list_zones():
if zone_name in zones:
self.log.info(
'sync: zone=%s already in config, ignoring',
zone_name,
)
continue
self.log.info('sync: adding dynamic zone=%s', zone_name)
zones[zone_name] = config
sourced_zones |= set(source.list_zones())
self.log.debug('_preprocess_zones: sourced_zones=%s', sourced_zones)
if name[-1] == '$':
# it's an end-anchored regex
re = re_compile(name)
# filter the zones we sourced with it
sourced_zones = set(z for z in sourced_zones if re.match(z))
# old-style wildcards are implcit catch-alls so they don't need
# filtering
# we do want to remove any explicitly configured zones or those
# that matched a previous wildcard/regex
sourced_zones -= set(zones.keys())
self.log.debug('_preprocess_zones: filtered=%s', sourced_zones)
for match in sourced_zones:
self.log.info('sync: adding dynamic zone=%s', match)
zones[match] = config
# remove the dynamic config element so we don't try and populate it # remove the dynamic config element so we don't try and populate it
del zones[name] del zones[name]


+ 33
- 0
tests/test_octodns_manager.py View File

@ -1394,6 +1394,39 @@ class TestManager(TestCase):
) )
mock_source.list_zones.assert_called_once() mock_source.list_zones.assert_called_once()
# doesn't matter what the actual name is, just that it starts with a *,
mock_source.reset_mock()
config = {'foo': 42}
zones = {'*SDFLKJSDFL': config, 'two': {'bar': 43}}
mock_source.list_zones.return_value = ['one', 'two', 'three']
got = manager._preprocess_zones(zones, sources=[mock_source])
self.assertEqual(
{'one': config, 'two': {'bar': 43}, 'three': config}, got
)
mock_source.list_zones.assert_called_once()
# multiple wildcards, this didn't make sense previously as the 2nd one
# would just win
mock_source.reset_mock()
config_a = {'foo': 42}
config_b = {'bar': 43}
zones = {r'.*\.a\.com\.$': config_a, r'.*\.b\.com\.$': config_b}
mock_source.list_zones.return_value = [
'one.a.com.',
'two.a.com.',
'three.b.com.',
]
got = manager._preprocess_zones(zones, sources=[mock_source])
self.assertEqual(
{
'one.a.com.': config_a,
'two.a.com.': config_a,
'three.b.com.': config_b,
},
got,
)
self.assertEqual(2, mock_source.list_zones.call_count)
class TestMainThreadExecutor(TestCase): class TestMainThreadExecutor(TestCase):
def test_success(self): def test_success(self):


Loading…
Cancel
Save