diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 966e96e..10add5a 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -28,7 +28,79 @@ class YamlProvider(BaseProvider): default_ttl: 3600 # Whether or not to enforce sorting order on the yaml config # (optional, default True) - enforce_order: True + enforce_order: true + # Whether duplicate records should replace rather than error + # (optiona, default False) + populate_should_replace: false + + Overriding values can be accomplished using multiple yaml providers in the + `sources` list where subsequent providers have `populate_should_replace` + set to `true`. An example use of this would be a zone that you want to push + to external DNS providers and internally, but you want to modify some of + the records in the internal version. + + config/octodns.com.yaml + --- + other: + type: A + values: + - 192.30.252.115 + - 192.30.252.116 + www: + type: A + values: + - 192.30.252.113 + - 192.30.252.114 + + + internal/octodns.com.yaml + --- + 'www': + type: A + values: + - 10.0.0.12 + - 10.0.0.13 + + external.yaml + --- + providers: + config: + class: octodns.provider.yaml.YamlProvider + directory: ./config + + zones: + + octodns.com.: + sources: + - config + targets: + - route53 + + internal.yaml + --- + providers: + config: + class: octodns.provider.yaml.YamlProvider + directory: ./config + + internal: + class: octodns.provider.yaml.YamlProvider + directory: ./internal + populate_should_replace: true + + zones: + + octodns.com.: + sources: + - config + - internal + targets: + - pdns + + You can then sync our records eternally with `--config-file=external.yaml` + and internally (with the custom overrides) with + `--config-file=internal.yaml` + ''' SUPPORTS_GEO = True SUPPORTS_DYNAMIC = True @@ -36,16 +108,18 @@ class YamlProvider(BaseProvider): 'PTR', 'SSHFP', 'SPF', 'SRV', 'TXT')) def __init__(self, id, directory, default_ttl=3600, enforce_order=True, - *args, **kwargs): + populate_should_replace=False, *args, **kwargs): self.log = logging.getLogger('{}[{}]'.format( self.__class__.__name__, id)) self.log.debug('__init__: id=%s, directory=%s, default_ttl=%d, ' - 'enforce_order=%d', id, directory, default_ttl, - enforce_order) + 'enforce_order=%d, populate_should_replace=%d', + id, directory, default_ttl, enforce_order, + populate_should_replace) super(YamlProvider, self).__init__(id, *args, **kwargs) self.directory = directory self.default_ttl = default_ttl self.enforce_order = enforce_order + self.populate_should_replace = populate_should_replace def _populate_from_file(self, filename, zone, lenient): with open(filename, 'r') as fh: @@ -59,9 +133,10 @@ class YamlProvider(BaseProvider): d['ttl'] = self.default_ttl record = Record.new(zone, name, d, source=self, lenient=lenient) - zone.add_record(record, lenient=lenient) - self.log.debug( - '_populate_from_file: successfully loaded "%s"', filename) + zone.add_record(record, lenient=lenient, + replace=self.populate_should_replace) + self.log.debug('_populate_from_file: successfully loaded "%s"', + filename) def populate(self, zone, target=False, lenient=False): self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, diff --git a/tests/config/override/dynamic.tests.yaml b/tests/config/override/dynamic.tests.yaml new file mode 100644 index 0000000..d79e092 --- /dev/null +++ b/tests/config/override/dynamic.tests.yaml @@ -0,0 +1,13 @@ +--- +# Replace 'a' with a generic record +a: + type: A + values: + - 4.4.4.4 + - 5.5.5.5 +# Add another record +added: + type: A + values: + - 6.6.6.6 + - 7.7.7.7 diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index a47dca1..f858c05 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -370,3 +370,36 @@ class TestSplitYamlProvider(TestCase): source.populate(zone) self.assertEquals('Record www.sub.unit.tests. is under a managed ' 'subzone', text_type(ctx.exception)) + + +class TestOverridingYamlProvider(TestCase): + + def test_provider(self): + config = join(dirname(__file__), 'config') + override_config = join(dirname(__file__), 'config', 'override') + base = YamlProvider('base', config, populate_should_replace=False) + override = YamlProvider('test', override_config, + populate_should_replace=True) + + zone = Zone('dynamic.tests.', []) + + # Load the base, should see the 5 records + base.populate(zone) + got = {r.name: r for r in zone.records} + self.assertEquals(5, len(got)) + # We get the "dynamic" A from the bae config + self.assertTrue('dynamic' in got['a'].data) + # No added + self.assertFalse('added' in got) + + # Load the overrides, should replace one and add 1 + override.populate(zone) + got = {r.name: r for r in zone.records} + self.assertEquals(6, len(got)) + # 'a' was replaced with a generic record + self.assertEquals({ + 'ttl': 3600, + 'values': ['4.4.4.4', '5.5.5.5'] + }, got['a'].data) + # And we have the new one + self.assertTrue('added' in got)