From 7359a57aaf31e452221eec77a43d7b2122b6e296 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 8 Sep 2025 13:27:53 -0700 Subject: [PATCH 1/2] Allow CNAME to coexist if all records have lenient=True --- .../b15794db1fe142b18e5a7da21abc76cc.md | 4 ++ octodns/zone.py | 15 ++++-- tests/test_octodns_zone.py | 47 +++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .changelog/b15794db1fe142b18e5a7da21abc76cc.md diff --git a/.changelog/b15794db1fe142b18e5a7da21abc76cc.md b/.changelog/b15794db1fe142b18e5a7da21abc76cc.md new file mode 100644 index 0000000..9a210be --- /dev/null +++ b/.changelog/b15794db1fe142b18e5a7da21abc76cc.md @@ -0,0 +1,4 @@ +--- +type: minor +--- +Allow CNAME to coexist if all records have lenient=True \ No newline at end of file diff --git a/octodns/zone.py b/octodns/zone.py index a94cda9..a8e6854 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -184,6 +184,8 @@ class Zone(object): self._records[name].discard(record) node = self._records[name] + new_lenient = record.lenient + existing_lenient = all(r.lenient for r in node) if record in node: # We already have a record at this node of this type existing = [c for c in node if c == record][0] @@ -192,9 +194,16 @@ class Zone(object): existing, record, ) - elif not lenient and ( - (record._type == 'CNAME' and len(node) > 0) - or ('CNAME' in [r._type for r in node]) + elif ( + # add was not called with lenience + not lenient + # existing and new records aren't lenient + and not (existing_lenient and new_lenient) + # new record or ecisting record are a CNAME + and ( + (record._type == 'CNAME' and len(node) > 0) + or ('CNAME' in [r._type for r in node]) + ) ): # We're adding a CNAME to existing records or adding to an existing # CNAME diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index 16789d2..758a417 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -514,6 +514,53 @@ class TestZone(TestCase): zone.add_record(a, lenient=True) self.assertEqual(set([a, cname]), zone.records) + # add cname to lenient a + a = Record.new( + zone, + 'www', + { + 'ttl': 60, + 'type': 'A', + 'value': '9.9.9.9', + 'octodns': {'lenient': True}, + }, + ) + zone = Zone('unit.tests.', []) + zone.add_record(a) + with self.assertRaises(InvalidNodeException) as ctx: + zone.add_record(cname) + self.assertTrue(', has some context' in str(ctx.exception)) + self.assertEqual(set([a]), zone.records) + + # add lenient a to cname + zone = Zone('unit.tests.', []) + zone.add_record(cname) + with self.assertRaises(InvalidNodeException): + zone.add_record(a) + self.assertEqual(set([cname]), zone.records) + + # add lenient cname to lenient a + cname = Record.new( + zone, + 'www', + { + 'ttl': 60, + 'type': 'CNAME', + 'value': 'foo.bar.com.', + 'octodns': {'lenient': True}, + }, + ) + zone = Zone('unit.tests.', []) + zone.add_record(a) + zone.add_record(cname) + self.assertEqual(set([a, cname]), zone.records) + + # add lenient a to lenient cname + zone = Zone('unit.tests.', []) + zone.add_record(cname) + zone.add_record(a) + self.assertEqual(set([a, cname]), zone.records) + def test_excluded_records(self): zone_normal = Zone('unit.tests.', []) zone_excluded = Zone('unit.tests.', []) From 30dd61dcd0cd4f2f947f051e0020b8726dcfbdce Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 8 Sep 2025 15:00:15 -0700 Subject: [PATCH 2/2] Improve leneience logic comment, further test cases --- octodns/zone.py | 2 +- tests/test_octodns_zone.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/octodns/zone.py b/octodns/zone.py index a8e6854..9d975cd 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -199,7 +199,7 @@ class Zone(object): not lenient # existing and new records aren't lenient and not (existing_lenient and new_lenient) - # new record or ecisting record are a CNAME + # and there'll be a CNAME co-existing with other records and ( (record._type == 'CNAME' and len(node) > 0) or ('CNAME' in [r._type for r in node]) diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index 758a417..3ba7aa3 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -561,6 +561,34 @@ class TestZone(TestCase): zone.add_record(a) self.assertEqual(set([a, cname]), zone.records) + # adding something else w/o lenient still errors + zone = Zone('unit.tests.', []) + zone.add_record(cname) + zone.add_record(a) + txt = Record.new( + zone, 'www', {'ttl': 60, 'type': 'TXT', 'value': 'Hello World'} + ) + with self.assertRaises(InvalidNodeException): + zone.add_record(txt) + self.assertEqual(set([a, cname]), zone.records) + + # if the 3rd record is lenient it can be added + zone = Zone('unit.tests.', []) + zone.add_record(cname) + zone.add_record(a) + txt = Record.new( + zone, + 'www', + { + 'ttl': 60, + 'type': 'TXT', + 'value': 'Hello World', + 'octodns': {'lenient': True}, + }, + ) + zone.add_record(txt) + self.assertEqual(set([a, cname, txt]), zone.records) + def test_excluded_records(self): zone_normal = Zone('unit.tests.', []) zone_excluded = Zone('unit.tests.', [])