From 0eb4e6663429b22bb5ef572c7c446682c7ff938d Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Wed, 7 Sep 2022 15:24:17 -0700 Subject: [PATCH] Naptr to/from rr text --- octodns/record/__init__.py | 41 ++++++++++++++++++++ tests/test_octodns_record.py | 73 +++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index a8f756f..86e2ef5 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -1376,12 +1376,47 @@ Record.register_type(MxRecord) class NaptrValue(EqualityTupleMixin, dict): VALID_FLAGS = ('S', 'A', 'U', 'P') + @classmethod + def parse_rr_text(self, value): + try: + ( + order, + preference, + flags, + service, + regexp, + replacement, + ) = value.split(' ') + except ValueError: + raise RrParseError('failed to parse string value as RR text') + try: + order = int(order) + preference = int(preference) + except ValueError: + pass + return { + 'order': order, + 'preference': preference, + 'flags': flags, + 'service': service, + 'regexp': regexp, + 'replacement': replacement, + } + @classmethod def validate(cls, data, _type): if not isinstance(data, (list, tuple)): data = (data,) reasons = [] for value in data: + if isinstance(value, str): + # it's hopefully RR formatted, give parsing a try + try: + value = cls.parse_rr_text(value) + except RrParseError as e: + reasons.append(str(e)) + # not a dict so no point in continuing + continue try: int(value['order']) except KeyError: @@ -1413,6 +1448,8 @@ class NaptrValue(EqualityTupleMixin, dict): return [cls(v) for v in values] def __init__(self, value): + if isinstance(value, str): + value = self.parse_rr_text(value) super().__init__( { 'order': int(value['order']), @@ -1476,6 +1513,10 @@ class NaptrValue(EqualityTupleMixin, dict): def data(self): return self + @property + def rr_text(self): + return f'{self.order} {self.preference} {self.flags} {self.service} {self.regexp} {self.replacement}' + def __hash__(self): return hash(self.__repr__()) diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 5ec7b71..1fc9db6 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -620,7 +620,7 @@ class TestRecord(TestCase): ) # make sure that validate is using parse_rr_text when passed string - # values + # value(s) reasons = MxRecord.validate( 'mx', 'mx.unit.tests.', {'ttl': 32, 'value': ''} ) @@ -638,8 +638,8 @@ class TestRecord(TestCase): zone = Zone('unit.tests.', []) a = MxRecord(zone, 'mx', {'ttl': 32, 'value': '10 mail.unit.tests.'}) self.assertEqual(10, a.values[0].preference) - self.assertEqual('10 mail.unit.tests.', a.values[0].rr_text) self.assertEqual('mail.unit.tests.', a.values[0].exchange) + self.assertEqual('10 mail.unit.tests.', a.values[0].rr_text) a = MxRecord( zone, 'mx', @@ -945,6 +945,75 @@ class TestRecord(TestCase): o.replacement = '1' self.assertEqual('1', o.replacement) + def test_naptr_rr_text(self): + # things with the wrong number of words won't parse + for v in ( + '', + 'one', + 'one two', + 'one two three', + 'one two three four', + 'one two three four five', + 'one two three four five six seven', + ): + with self.assertRaises(RrParseError): + NaptrValue.parse_rr_text(v) + + # we don't care if the types of things are correct when parsing rr text + self.assertEqual( + { + 'order': 'one', + 'preference': 'two', + 'flags': 'three', + 'service': 'four', + 'regexp': 'five', + 'replacement': 'six', + }, + NaptrValue.parse_rr_text('one two three four five six'), + ) + + # order and preference will be converted to int's when possible + self.assertEqual( + { + 'order': 1, + 'preference': 2, + 'flags': 'three', + 'service': 'four', + 'regexp': 'five', + 'replacement': 'six', + }, + NaptrValue.parse_rr_text('1 2 three four five six'), + ) + + # make sure that validate is using parse_rr_text when passed string + # value(s) + reasons = NaptrRecord.validate( + 'naptr', 'naptr.unit.tests.', {'ttl': 32, 'value': ''} + ) + self.assertEqual(['failed to parse string value as RR text'], reasons) + reasons = NaptrRecord.validate( + 'naptr', 'naptr.unit.tests.', {'ttl': 32, 'value': ['']} + ) + self.assertEqual(['failed to parse string value as RR text'], reasons) + reasons = NaptrRecord.validate( + 'naptr', + 'naptr.unit.tests.', + {'ttl': 32, 'value': ['1 2 S service regexp replacement']}, + ) + self.assertFalse(reasons) + + # make sure that the cstor is using parse_rr_text + zone = Zone('unit.tests.', []) + s = '1 2 S service regexp replacement' + a = NaptrRecord(zone, 'naptr', {'ttl': 32, 'value': s}) + self.assertEqual(1, a.values[0].order) + self.assertEqual(2, a.values[0].preference) + self.assertEqual('S', a.values[0].flags) + self.assertEqual('service', a.values[0].service) + self.assertEqual('regexp', a.values[0].regexp) + self.assertEqual('replacement', a.values[0].replacement) + self.assertEqual(s, a.values[0].rr_text) + def test_ns(self): a_values = ['5.6.7.8.', '6.7.8.9.', '7.8.9.0.'] a_data = {'ttl': 30, 'values': a_values}