diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 93b9b12..a284be4 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -17,6 +17,7 @@ import logging from ..record import Record from ..yaml import safe_load, safe_dump from .base import BaseProvider +from . import ProviderException class YamlProvider(BaseProvider): @@ -207,12 +208,20 @@ class YamlProvider(BaseProvider): return False before = len(zone.records) - filename = join(self.directory, f'{zone.decoded_name}yaml') - if not isfile(filename): - idna_filename = join(self.directory, f'{zone.name}yaml') + utf8_filename = join(self.directory, f'{zone.decoded_name}yaml') + idna_filename = join(self.directory, f'{zone.name}yaml') + + # we prefer utf8 + if isfile(utf8_filename): + if utf8_filename != idna_filename and isfile(idna_filename): + raise ProviderException( + f'Both UTF-8 "{utf8_filename}" and IDNA "{idna_filename}" exist for {zone.decoded_name}' + ) + filename = utf8_filename + else: self.log.warning( - 'populate: "%s" does not exist, falling back to idna version "%s"', - filename, + 'populate: "%s" does not exist, falling back to try idna version "%s"', + utf8_filename, idna_filename, ) filename = idna_filename @@ -245,7 +254,7 @@ class YamlProvider(BaseProvider): del d['ttl'] if record._octodns: d['octodns'] = record._octodns - data[record.name].append(d) + data[record.decoded_name].append(d) # Flatten single element lists for k in data.keys(): @@ -261,7 +270,7 @@ class YamlProvider(BaseProvider): filename = join(self.directory, f'{desired.decoded_name}yaml') self.log.debug('_apply: writing filename=%s', filename) with open(filename, 'w') as fh: - safe_dump(dict(data), fh) + safe_dump(dict(data), fh, allow_unicode=True) def _list_all_yaml_files(directory): diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index e2f55c2..d0a6358 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -15,7 +15,9 @@ from unittest import TestCase from yaml import safe_load from yaml.constructor import ConstructorError +from octodns.idna import idna_encode from octodns.record import Create +from octodns.provider import ProviderException from octodns.provider.base import Plan from octodns.provider.yaml import ( _list_all_yaml_files, @@ -172,6 +174,40 @@ class TestYamlProvider(TestCase): # make sure nothing is left self.assertEqual([], list(data.keys())) + def test_idna_filenames(self): + with TemporaryDirectory() as td: + name = 'déjà.vu.' + filename = f'{name}yaml' + + provider = YamlProvider('test', td.dirname) + zone = Zone(idna_encode(name), []) + + # create a idna named file + with open(join(td.dirname, idna_encode(filename)), 'w') as fh: + pass + + fh.write( + '''--- +'': + type: A + value: 1.2.3.4 +''' + ) + + # populates fine when there's just the idna version (as a fallback) + provider.populate(zone) + self.assertEqual(1, len(zone.records)) + + # create a utf8 named file + with open(join(td.dirname, filename), 'w') as fh: + pass + + # does not allow both idna and utf8 named files + with self.assertRaises(ProviderException) as ctx: + provider.populate(zone) + msg = str(ctx.exception) + self.assertTrue('Both UTF-8' in msg) + def test_empty(self): source = YamlProvider( 'test', join(dirname(__file__), 'config'), supports_root_ns=False