Browse Source

Merge pull request #1083 from octodns/filter-processors

ZoneNameFilter processor
pull/1087/head
Ross McFarland 2 years ago
committed by GitHub
parent
commit
7ff07f503e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 181 additions and 59 deletions
  1. +5
    -0
      CHANGELOG.md
  2. +107
    -59
      octodns/processor/filter.py
  3. +69
    -0
      tests/test_octodns_processor_filter.py

+ 5
- 0
CHANGELOG.md View File

@ -1,3 +1,8 @@
## v1.3.0 - 2023-??-?? - ???
* Added ZoneNameFilter processor to enable ignoring/alerting on type-os like
octodns.com.octodns.com
## v1.2.1 - 2023-09-29 - Now with fewer stale files
* Update script/release to do clean room dist builds


+ 107
- 59
octodns/processor/filter.py View File

@ -4,10 +4,45 @@
from re import compile as re_compile
from ..record.exception import ValidationError
from .base import BaseProcessor
class TypeAllowlistFilter(BaseProcessor):
class AllowsMixin:
def matches(self, zone, record):
pass
def doesnt_match(self, zone, record):
zone.remove_record(record)
class RejectsMixin:
def matches(self, zone, record):
zone.remove_record(record)
def doesnt_match(self, zone, record):
pass
class _TypeBaseFilter(BaseProcessor):
def __init__(self, name, _list):
super().__init__(name)
self._list = set(_list)
def _process(self, zone, *args, **kwargs):
for record in zone.records:
if record._type in self._list:
self.matches(zone, record)
else:
self.doesnt_match(zone, record)
return zone
process_source_zone = _process
process_target_zone = _process
class TypeAllowlistFilter(_TypeBaseFilter, AllowsMixin):
'''Only manage records of the specified type(s).
Example usage:
@ -30,21 +65,10 @@ class TypeAllowlistFilter(BaseProcessor):
'''
def __init__(self, name, allowlist):
super().__init__(name)
self.allowlist = set(allowlist)
def _process(self, zone, *args, **kwargs):
for record in zone.records:
if record._type not in self.allowlist:
zone.remove_record(record)
return zone
process_source_zone = _process
process_target_zone = _process
super().__init__(name, allowlist)
class TypeRejectlistFilter(BaseProcessor):
class TypeRejectlistFilter(_TypeBaseFilter, RejectsMixin):
'''Ignore records of the specified type(s).
Example usage:
@ -66,18 +90,7 @@ class TypeRejectlistFilter(BaseProcessor):
'''
def __init__(self, name, rejectlist):
super().__init__(name)
self.rejectlist = set(rejectlist)
def _process(self, zone, *args, **kwargs):
for record in zone.records:
if record._type in self.rejectlist:
zone.remove_record(record)
return zone
process_source_zone = _process
process_target_zone = _process
super().__init__(name, rejectlist)
class _NameBaseFilter(BaseProcessor):
@ -93,8 +106,25 @@ class _NameBaseFilter(BaseProcessor):
self.exact = exact
self.regex = regex
def _process(self, zone, *args, **kwargs):
for record in zone.records:
name = record.name
if name in self.exact:
self.matches(zone, record)
continue
elif any(r.search(name) for r in self.regex):
self.matches(zone, record)
continue
self.doesnt_match(zone, record)
return zone
process_source_zone = _process
process_target_zone = _process
class NameAllowlistFilter(_NameBaseFilter):
class NameAllowlistFilter(_NameBaseFilter, AllowsMixin):
'''Only manage records with names that match the provider patterns
Example usage:
@ -125,23 +155,8 @@ class NameAllowlistFilter(_NameBaseFilter):
def __init__(self, name, allowlist):
super().__init__(name, allowlist)
def _process(self, zone, *args, **kwargs):
for record in zone.records:
name = record.name
if name in self.exact:
continue
elif any(r.search(name) for r in self.regex):
continue
zone.remove_record(record)
return zone
process_source_zone = _process
process_target_zone = _process
class NameRejectlistFilter(_NameBaseFilter):
class NameRejectlistFilter(_NameBaseFilter, RejectsMixin):
'''Reject managing records with names that match the provider patterns
Example usage:
@ -172,17 +187,30 @@ class NameRejectlistFilter(_NameBaseFilter):
def __init__(self, name, rejectlist):
super().__init__(name, rejectlist)
class IgnoreRootNsFilter(BaseProcessor):
'''Do not manage Root NS Records.
Example usage:
processors:
no-root-ns:
class: octodns.processor.filter.IgnoreRootNsFilter
zones:
exxampled.com.:
sources:
- config
processors:
- no-root-ns
targets:
- ns1
'''
def _process(self, zone, *args, **kwargs):
for record in zone.records:
name = record.name
if name in self.exact:
if record._type == 'NS' and not record.name:
zone.remove_record(record)
continue
for regex in self.regex:
if regex.search(name):
zone.remove_record(record)
break
return zone
@ -190,29 +218,49 @@ class NameRejectlistFilter(_NameBaseFilter):
process_target_zone = _process
class IgnoreRootNsFilter(BaseProcessor):
'''Do not manage Root NS Records.
class ZoneNameFilter(BaseProcessor):
'''Filter or error on record names that contain the zone name
Example usage:
processors:
no-root-ns:
class: octodns.processor.filter.IgnoreRootNsFilter
zone-name:
class: octodns.processor.filter.ZoneNameFilter
# If true a ValidationError will be throw when such records are
# encouterd, if false the records will just be ignored/omitted.
# (default: true)
zones:
exxampled.com.:
sources:
- config
processors:
- no-root-ns
- zone-name
targets:
- ns1
- azure
'''
def __init__(self, name, error=True):
super().__init__(name)
self.error = error
def _process(self, zone, *args, **kwargs):
zone_name_with_dot = zone.name
zone_name_without_dot = zone_name_with_dot[:-1]
for record in zone.records:
if record._type == 'NS' and not record.name:
zone.remove_record(record)
name = record.name
if name.endswith(zone_name_with_dot) or name.endswith(
zone_name_without_dot
):
if self.error:
raise ValidationError(
record.fqdn,
['record name ends with zone name'],
record.context,
)
else:
# just remove it
zone.remove_record(record)
return zone


+ 69
- 0
tests/test_octodns_processor_filter.py View File

@ -10,8 +10,10 @@ from octodns.processor.filter import (
NameRejectlistFilter,
TypeAllowlistFilter,
TypeRejectlistFilter,
ZoneNameFilter,
)
from octodns.record import Record
from octodns.record.exception import ValidationError
from octodns.zone import Zone
zone = Zone('unit.tests.', [])
@ -180,3 +182,70 @@ class TestIgnoreRootNsFilter(TestCase):
[('A', ''), ('NS', 'sub')],
sorted([(r._type, r.name) for r in filtered.records]),
)
class TestZoneNameFilter(TestCase):
def test_ends_with_zone(self):
zone_name_filter = ZoneNameFilter('zone-name', error=False)
zone = Zone('unit.tests.', [])
# something that doesn't come into play
zone.add_record(
Record.new(
zone, 'www', {'type': 'A', 'ttl': 43, 'value': '1.2.3.4'}
)
)
# something that has the zone name, but doesn't end with it
zone.add_record(
Record.new(
zone,
f'{zone.name}more',
{'type': 'A', 'ttl': 43, 'value': '1.2.3.4'},
)
)
self.assertEqual(2, len(zone.records))
filtered = zone_name_filter.process_source_zone(zone.copy())
# get everything back
self.assertEqual(2, len(filtered.records))
with_dot = zone.copy()
with_dot.add_record(
Record.new(
zone, zone.name, {'type': 'A', 'ttl': 43, 'value': '1.2.3.4'}
)
)
self.assertEqual(3, len(with_dot.records))
filtered = zone_name_filter.process_source_zone(with_dot.copy())
# don't get the one that ends with the zone name
self.assertEqual(2, len(filtered.records))
without_dot = zone.copy()
without_dot.add_record(
Record.new(
zone,
zone.name[:-1],
{'type': 'A', 'ttl': 43, 'value': '1.2.3.4'},
)
)
self.assertEqual(3, len(without_dot.records))
filtered = zone_name_filter.process_source_zone(without_dot.copy())
# don't get the one that ends with the zone name
self.assertEqual(2, len(filtered.records))
def test_error(self):
errors = ZoneNameFilter('zone-name', error=True)
zone = Zone('unit.tests.', [])
zone.add_record(
Record.new(
zone, zone.name, {'type': 'A', 'ttl': 43, 'value': '1.2.3.4'}
)
)
with self.assertRaises(ValidationError) as ctx:
errors.process_source_zone(zone)
self.assertEqual(
['record name ends with zone name'], ctx.exception.reasons
)

Loading…
Cancel
Save