diff --git a/octodns/manager.py b/octodns/manager.py index d4d23bf..dd36e9a 100644 --- a/octodns/manager.py +++ b/octodns/manager.py @@ -119,7 +119,13 @@ class Manager(object): manager_config, enable_checksum ) + # add our hard-coded environ handler first so that other secret + # providers can pull in env variables w/it self.secret_handlers = {'env': EnvironSecrets('env')} + secret_handlers_config = self.config.get('secret_handlers', {}) + self.secret_handlers.update( + self._config_secret_handlers(secret_handlers_config) + ) self.auto_arpa = self._config_auto_arpa(manager_config, auto_arpa) @@ -221,6 +227,38 @@ class Manager(object): self.log.info('_config_auto_arpa: auto_arpa=%s', auto_arpa) return auto_arpa + def _config_secret_handlers(self, secret_handlers_config): + self.log.debug('_config_secret_handlers: configuring secret_handlers') + secret_handlers = {} + for sh_name, sh_config in secret_handlers_config.items(): + # Get our class and remove it from the secret handler config + try: + _class = sh_config.pop('class') + except KeyError: + self.log.exception('Invalid secret handler class') + raise ManagerException( + f'Secret Handler {sh_name} is missing class, {sh_config.context}' + ) + _class, module, version = self._get_named_class( + 'secret handler', _class, sh_config.context + ) + kwargs = self._build_kwargs(sh_config) + try: + secret_handlers[sh_name] = _class(sh_name, **kwargs) + self.log.info( + '__init__: secret_handler=%s (%s %s)', + sh_name, + module, + version, + ) + except TypeError: + self.log.exception('Invalid secret handler config') + raise ManagerException( + f'Incorrect secret handler config for {sh_name}, {sh_config.context}' + ) + + return secret_handlers + def _config_providers(self, providers_config): self.log.debug('_config_providers: configuring providers') providers = {} diff --git a/tests/helpers.py b/tests/helpers.py index 019395b..5ec8761 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -9,6 +9,7 @@ from tempfile import mkdtemp from octodns.processor.base import BaseProcessor from octodns.provider.base import BaseProvider from octodns.provider.yaml import YamlProvider +from octodns.secret.base import BaseSecrets class SimpleSource(object): @@ -134,3 +135,12 @@ class CountingProcessor(BaseProcessor): def process_source_zone(self, zone, *args, **kwargs): self.count += len(zone.records) return zone + + +class DummySecrets(BaseSecrets): + def __init__(self, name, prefix): + super().__init__(name) + self.prefix = prefix + + def fetch(self, name, source): + return f'{self.prefix}{name}' diff --git a/tests/test_octodns_manager.py b/tests/test_octodns_manager.py index 008f23a..20be8a6 100644 --- a/tests/test_octodns_manager.py +++ b/tests/test_octodns_manager.py @@ -17,6 +17,7 @@ from helpers import ( ) from octodns import __version__ +from octodns.context import ContextDict from octodns.idna import IdnaDict, idna_encode from octodns.manager import ( MainThreadExecutor, @@ -1204,6 +1205,50 @@ class TestManager(TestCase): ), ) + def test_config_secret_handlers(self): + # config doesn't matter here + manager = Manager(get_config_filename('simple.yaml')) + + # no config + self.assertEqual({}, manager._config_secret_handlers({})) + + # missing class + with self.assertRaises(ManagerException) as ctx: + cfg = {'secr3t': ContextDict({}, context='xyz')} + manager._config_secret_handlers(cfg) + self.assertEqual( + 'Secret Handler secr3t is missing class, xyz', str(ctx.exception) + ) + + # bad param + with self.assertRaises(ManagerException) as ctx: + cfg = { + 'secr3t': ContextDict( + { + 'class': 'octodns.secret.environ.EnvironSecrets', + 'bad': 'param', + }, + context='xyz', + ) + } + manager._config_secret_handlers(cfg) + self.assertEqual( + 'Incorrect secret handler config for secr3t, xyz', + str(ctx.exception), + ) + + # valid with a param that gets used/tested + cfg = { + 'secr3t': ContextDict( + {'class': 'helpers.DummySecrets', 'prefix': 'pre-'}, + context='xyz', + ) + } + shs = manager._config_secret_handlers(cfg) + sh = shs.get('secr3t') + self.assertTrue(sh) + self.assertEqual('pre-thing', sh.fetch('thing', None)) + class TestMainThreadExecutor(TestCase): def test_success(self):