diff --git a/CHANGELOG.md b/CHANGELOG.md index 22840c0..19c4aa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ * Include the octodns special section info in Record __repr__, makes it easier to debug things with providers that have special functionality configured there. +* Most processor.filter processors now support an include_target flag that can + be set to False to leave the target zone data untouched, thus remove any + existing filtered records. Default behavior is unchanged and filtered records + will be completely invisible to octoDNS ## v1.2.1 - 2023-09-29 - Now with fewer stale files diff --git a/octodns/processor/filter.py b/octodns/processor/filter.py index e7913d8..cd1c82d 100644 --- a/octodns/processor/filter.py +++ b/octodns/processor/filter.py @@ -11,6 +11,20 @@ from ..record.exception import ValidationError from .base import BaseProcessor +class _FilterProcessor(BaseProcessor): + def __init__(self, name, include_target=True, **kwargs): + super().__init__(name, **kwargs) + self.include_target = include_target + + def process_source_zone(self, *args, **kwargs): + return self._process(*args, **kwargs) + + def process_target_zone(self, existing, *args, **kwargs): + if self.include_target: + return self._process(existing, *args, **kwargs) + return existing + + class AllowsMixin: def matches(self, zone, record): pass @@ -27,9 +41,9 @@ class RejectsMixin: pass -class _TypeBaseFilter(BaseProcessor): - def __init__(self, name, _list): - super().__init__(name) +class _TypeBaseFilter(_FilterProcessor): + def __init__(self, name, _list, **kwargs): + super().__init__(name, **kwargs) self._list = set(_list) def _process(self, zone, *args, **kwargs): @@ -41,9 +55,6 @@ class _TypeBaseFilter(BaseProcessor): return zone - process_source_zone = _process - process_target_zone = _process - class TypeAllowlistFilter(_TypeBaseFilter, AllowsMixin): '''Only manage records of the specified type(s). @@ -56,6 +67,10 @@ class TypeAllowlistFilter(_TypeBaseFilter, AllowsMixin): allowlist: - A - AAAA + # Optional param that can be set to False to leave the target zone + # alone, thus allowing deletion of existing records + # (default: true) + # include_target: True zones: exxampled.com.: @@ -67,8 +82,8 @@ class TypeAllowlistFilter(_TypeBaseFilter, AllowsMixin): - ns1 ''' - def __init__(self, name, allowlist): - super().__init__(name, allowlist) + def __init__(self, name, allowlist, **kwargs): + super().__init__(name, allowlist, **kwargs) class TypeRejectlistFilter(_TypeBaseFilter, RejectsMixin): @@ -81,6 +96,10 @@ class TypeRejectlistFilter(_TypeBaseFilter, RejectsMixin): class: octodns.processor.filter.TypeRejectlistFilter rejectlist: - CNAME + # Optional param that can be set to False to leave the target zone + # alone, thus allowing deletion of existing records + # (default: true) + # include_target: True zones: exxampled.com.: @@ -92,13 +111,13 @@ class TypeRejectlistFilter(_TypeBaseFilter, RejectsMixin): - route53 ''' - def __init__(self, name, rejectlist): - super().__init__(name, rejectlist) + def __init__(self, name, rejectlist, **kwargs): + super().__init__(name, rejectlist, **kwargs) -class _NameBaseFilter(BaseProcessor): - def __init__(self, name, _list): - super().__init__(name) +class _NameBaseFilter(_FilterProcessor): + def __init__(self, name, _list, **kwargs): + super().__init__(name, **kwargs) exact = set() regex = [] for pattern in _list: @@ -123,9 +142,6 @@ class _NameBaseFilter(BaseProcessor): return zone - process_source_zone = _process - process_target_zone = _process - class NameAllowlistFilter(_NameBaseFilter, AllowsMixin): '''Only manage records with names that match the provider patterns @@ -144,6 +160,10 @@ class NameAllowlistFilter(_NameBaseFilter, AllowsMixin): - /some-pattern-\\d\\+/ # regex - anchored so has to match start to end - /^start-.+-end$/ + # Optional param that can be set to False to leave the target zone + # alone, thus allowing deletion of existing records + # (default: true) + # include_target: True zones: exxampled.com.: @@ -176,6 +196,10 @@ class NameRejectlistFilter(_NameBaseFilter, RejectsMixin): - /some-pattern-\\d\\+/ # regex - anchored so has to match start to end - /^start-.+-end$/ + # Optional param that can be set to False to leave the target zone + # alone, thus allowing deletion of existing records + # (default: true) + # include_target: True zones: exxampled.com.: @@ -356,7 +380,7 @@ class ExcludeRootNsChanges(BaseProcessor): return plan -class ZoneNameFilter(BaseProcessor): +class ZoneNameFilter(_FilterProcessor): '''Filter or error on record names that contain the zone name Example usage: @@ -367,6 +391,10 @@ class ZoneNameFilter(BaseProcessor): # If true a ValidationError will be throw when such records are # encouterd, if false the records will just be ignored/omitted. # (default: true) + # Optional param that can be set to False to leave the target zone + # alone, thus allowing deletion of existing records + # (default: true) + # include_target: True zones: exxampled.com.: @@ -378,8 +406,8 @@ class ZoneNameFilter(BaseProcessor): - azure ''' - def __init__(self, name, error=True): - super().__init__(name) + def __init__(self, name, error=True, **kwargs): + super().__init__(name, **kwargs) self.error = error def _process(self, zone, *args, **kwargs): @@ -401,6 +429,3 @@ class ZoneNameFilter(BaseProcessor): zone.remove_record(record) return zone - - process_source_zone = _process - process_target_zone = _process diff --git a/tests/test_octodns_processor_filter.py b/tests/test_octodns_processor_filter.py index 7ee98a8..1880a16 100644 --- a/tests/test_octodns_processor_filter.py +++ b/tests/test_octodns_processor_filter.py @@ -56,6 +56,22 @@ class TestTypeAllowListFilter(TestCase): ['a', 'a2', 'aaaa'], sorted([r.name for r in got.records]) ) + def test_include_target(self): + filter_txt = TypeAllowlistFilter( + 'only-txt', ['TXT'], include_target=False + ) + + # as a source we don't see them + got = filter_txt.process_source_zone(zone.copy()) + self.assertEqual(['txt', 'txt2'], sorted([r.name for r in got.records])) + + # but as a target we do b/c it's not included + got = filter_txt.process_target_zone(zone.copy()) + self.assertEqual( + ['a', 'a2', 'aaaa', 'txt', 'txt2'], + sorted([r.name for r in got.records]), + ) + class TestTypeRejectListFilter(TestCase): def test_basics(self):