diff --git a/.changelog/af8522cac7e54d22a615eab351d445b3.md b/.changelog/af8522cac7e54d22a615eab351d445b3.md new file mode 100644 index 0000000..72130ef --- /dev/null +++ b/.changelog/af8522cac7e54d22a615eab351d445b3.md @@ -0,0 +1,4 @@ +--- +type: patch +--- +Improve error messaging for unknown templating parameters \ No newline at end of file diff --git a/octodns/processor/templating.py b/octodns/processor/templating.py index 2f74bc6..616a715 100644 --- a/octodns/processor/templating.py +++ b/octodns/processor/templating.py @@ -5,6 +5,14 @@ from octodns.processor.base import BaseProcessor +class TemplatingError(Exception): + + def __init__(self, record, msg): + self.record = record + msg = f'Invalid record "{record.fqdn}", {msg}' + super().__init__(msg) + + class Templating(BaseProcessor): ''' Record templating using python format. For simple records like TXT and CAA @@ -88,7 +96,7 @@ class Templating(BaseProcessor): }, } - def params(record): + def build_params(record): record_fqdn = record.decoded_fqdn record_decoded_fqdn = record.decoded_fqdn record_encoded_fqdn = record.fqdn @@ -109,12 +117,24 @@ class Templating(BaseProcessor): **zone_params, } + def template(value, params, record): + try: + return value.template(params) + except KeyError as e: + raise TemplatingError( + record, + f'undefined template parameter "{e.args[0]}" in value', + ) from e + for record in desired.records: + params = build_params(record) if hasattr(record, 'values'): if record.values and not hasattr(record.values[0], 'template'): # the (custom) value type does not support templating continue - new_values = [v.template(params(record)) for v in record.values] + new_values = [ + template(v, params, record) for v in record.values + ] if record.values != new_values: new = record.copy() new.values = new_values @@ -123,7 +143,7 @@ class Templating(BaseProcessor): if not hasattr(record.value, 'template'): # the (custom) value type does not support templating continue - new_value = record.value.template(params(record)) + new_value = template(record.value, params, record) if record.value != new_value: new = record.copy() new.value = new_value diff --git a/tests/test_octodns_processor_templating.py b/tests/test_octodns_processor_templating.py index 3c58174..f959964 100644 --- a/tests/test_octodns_processor_templating.py +++ b/tests/test_octodns_processor_templating.py @@ -5,7 +5,7 @@ from unittest import TestCase from unittest.mock import call, patch -from octodns.processor.templating import Templating +from octodns.processor.templating import Templating, TemplatingError from octodns.record import Record, ValueMixin, ValuesMixin from octodns.zone import Zone @@ -269,3 +269,45 @@ class TemplatingTest(TestCase): self.assertEqual('num_sources: 3', txt.values[0]) self.assertEqual('the_answer: 42', txt.values[1]) self.assertEqual('the_date: today', txt.values[2]) + + def test_bad_key(self): + templ = Templating('test') + + zone = Zone('unit.tests.', []) + txt = Record.new( + zone, + 'txt', + {'type': 'TXT', 'ttl': 42, 'value': 'this {bad} does not exist'}, + ) + zone.add_record(txt) + + with self.assertRaises(TemplatingError) as ctx: + templ.process_source_zone( + zone, tuple(DummySource(i) for i in range(3)) + ) + self.assertEqual( + 'Invalid record "txt.unit.tests.", undefined template parameter "bad" in value', + str(ctx.exception), + ) + + zone = Zone('unit.tests.', []) + cname = Record.new( + zone, + 'cname', + { + 'type': 'CNAME', + 'ttl': 42, + 'value': '_cname.{bad}something.else.', + }, + lenient=True, + ) + zone.add_record(cname) + + with self.assertRaises(TemplatingError) as ctx: + templ.process_source_zone( + zone, tuple(DummySource(i) for i in range(3)) + ) + self.assertEqual( + 'Invalid record "cname.unit.tests.", undefined template parameter "bad" in value', + str(ctx.exception), + )