mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Merge remote-tracking branch 'origin/main' into validate-values-not-string
This commit is contained in:
@@ -335,6 +335,8 @@ Similar to providers, but can only serve to populate records into a zone, cannot
|
||||
| [MetaProcessor](/octodns/processor/meta.py) | Adds a special meta record with timing, UUID, providers, and/or version to aid in debugging and monitoring. |
|
||||
| [NameAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified naming patterns, all others will be ignored |
|
||||
| [NameRejectlistFilter](/octodns/processor/filter.py) | Filter that INGORES records that match specified naming patterns, all others will be managed |
|
||||
| [ValueAllowlistFilter](/octodns/processor/filter.py) | Filter that ONLY manages records that match specified value patterns based on `rdata_text`, all others will be ignored |
|
||||
| [ValueRejectlistFilter](/octodns/processor/filter.py) | Filter that INGORES records that match specified value patterns based on `rdata_text`, all others will be managed |
|
||||
| [OwnershipProcessor](/octodns/processor/ownership.py) | Processor that implements ownership in octoDNS so that it can manage only the records in a zone in sources and will ignore all others. |
|
||||
| [SpfDnsLookupProcessor](/octodns/processor/spf.py) | Processor that checks SPF values for violations of DNS query limits |
|
||||
| [TtlRestrictionFilter](/octodns/processor/restrict.py) | Processor that restricts the allow TTL values to a specified range or list of specific values |
|
||||
|
||||
@@ -215,6 +215,105 @@ class NameRejectlistFilter(_NameBaseFilter, RejectsMixin):
|
||||
super().__init__(name, rejectlist)
|
||||
|
||||
|
||||
class _ValueBaseFilter(_FilterProcessor):
|
||||
def __init__(self, name, _list, **kwargs):
|
||||
super().__init__(name, **kwargs)
|
||||
exact = set()
|
||||
regex = []
|
||||
for pattern in _list:
|
||||
if pattern.startswith('/'):
|
||||
regex.append(re_compile(pattern[1:-1]))
|
||||
else:
|
||||
exact.add(pattern)
|
||||
self.exact = exact
|
||||
self.regex = regex
|
||||
|
||||
def _process(self, zone, *args, **kwargs):
|
||||
for record in zone.records:
|
||||
values = []
|
||||
if hasattr(record, 'values'):
|
||||
values = [value.rdata_text for value in record.values]
|
||||
else:
|
||||
values = [record.value.rdata_text]
|
||||
|
||||
if any(value in self.exact for value in values):
|
||||
self.matches(zone, record)
|
||||
continue
|
||||
elif any(r.search(value) for r in self.regex for value in values):
|
||||
self.matches(zone, record)
|
||||
continue
|
||||
|
||||
self.doesnt_match(zone, record)
|
||||
|
||||
return zone
|
||||
|
||||
|
||||
class ValueAllowlistFilter(_ValueBaseFilter, AllowsMixin):
|
||||
'''Only manage records with values that match the provider patterns
|
||||
|
||||
Example usage:
|
||||
|
||||
processors:
|
||||
only-these:
|
||||
class: octodns.processor.filter.ValueAllowlistFilter
|
||||
allowlist:
|
||||
# exact string match
|
||||
- www
|
||||
# contains/substring match
|
||||
- /substring/
|
||||
# regex pattern match
|
||||
- /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.:
|
||||
sources:
|
||||
- config
|
||||
processors:
|
||||
- only-these
|
||||
targets:
|
||||
- route53
|
||||
'''
|
||||
|
||||
|
||||
class ValueRejectlistFilter(_ValueBaseFilter, RejectsMixin):
|
||||
'''Reject managing records with names that match the provider patterns
|
||||
|
||||
Example usage:
|
||||
|
||||
processors:
|
||||
not-these:
|
||||
class: octodns.processor.filter.ValueRejectlistFilter
|
||||
rejectlist:
|
||||
# exact string match
|
||||
- www
|
||||
# contains/substring match
|
||||
- /substring/
|
||||
# regex pattern match
|
||||
- /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.:
|
||||
sources:
|
||||
- config
|
||||
processors:
|
||||
- not-these
|
||||
targets:
|
||||
- route53
|
||||
'''
|
||||
|
||||
|
||||
class _NetworkValueBaseFilter(BaseProcessor):
|
||||
def __init__(self, name, _list):
|
||||
super().__init__(name)
|
||||
|
||||
@@ -13,6 +13,8 @@ from octodns.processor.filter import (
|
||||
NetworkValueRejectlistFilter,
|
||||
TypeAllowlistFilter,
|
||||
TypeRejectlistFilter,
|
||||
ValueAllowlistFilter,
|
||||
ValueRejectlistFilter,
|
||||
ZoneNameFilter,
|
||||
)
|
||||
from octodns.provider.plan import Plan
|
||||
@@ -179,6 +181,146 @@ class TestNameRejectListFilter(TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestValueAllowListFilter(TestCase):
|
||||
zone = Zone('unit.tests.', [])
|
||||
matches = Record.new(
|
||||
zone,
|
||||
'good.exact',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'matches.example.com.'},
|
||||
)
|
||||
zone.add_record(matches)
|
||||
doesnt = Record.new(
|
||||
zone,
|
||||
'bad.exact',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'doesnt.example.com.'},
|
||||
)
|
||||
zone.add_record(doesnt)
|
||||
matches_many = Record.new(
|
||||
zone,
|
||||
'good.values',
|
||||
{
|
||||
'type': 'TXT',
|
||||
'ttl': 42,
|
||||
'values': ['matches.example.com.', 'another'],
|
||||
},
|
||||
)
|
||||
zone.add_record(matches_many)
|
||||
doesnt_many = Record.new(
|
||||
zone,
|
||||
'bad.values',
|
||||
{
|
||||
'type': 'TXT',
|
||||
'ttl': 42,
|
||||
'values': ['doesnt.example.com.', 'another'],
|
||||
},
|
||||
)
|
||||
zone.add_record(doesnt_many)
|
||||
matchable1 = Record.new(
|
||||
zone,
|
||||
'first.regex',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'start.f43ad96.end.'},
|
||||
)
|
||||
zone.add_record(matchable1)
|
||||
matchable2 = Record.new(
|
||||
zone,
|
||||
'second.regex',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'start.a3b444c.end.'},
|
||||
)
|
||||
zone.add_record(matchable2)
|
||||
|
||||
def test_exact(self):
|
||||
allows = ValueAllowlistFilter('exact', ('matches.example.com.',))
|
||||
|
||||
self.assertEqual(6, len(self.zone.records))
|
||||
filtered = allows.process_source_zone(self.zone.copy())
|
||||
self.assertEqual(2, len(filtered.records))
|
||||
self.assertEqual(
|
||||
['good.exact', 'good.values'],
|
||||
sorted([r.name for r in filtered.records]),
|
||||
)
|
||||
|
||||
def test_regex(self):
|
||||
allows = ValueAllowlistFilter('exact', ('/^start\\..+\\.end\\.$/',))
|
||||
|
||||
self.assertEqual(6, len(self.zone.records))
|
||||
filtered = allows.process_source_zone(self.zone.copy())
|
||||
self.assertEqual(2, len(filtered.records))
|
||||
self.assertEqual(
|
||||
['first.regex', 'second.regex'],
|
||||
sorted([r.name for r in filtered.records]),
|
||||
)
|
||||
|
||||
|
||||
class TestValueRejectListFilter(TestCase):
|
||||
zone = Zone('unit.tests.', [])
|
||||
matches = Record.new(
|
||||
zone,
|
||||
'good.compare',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'matches.example.com.'},
|
||||
)
|
||||
zone.add_record(matches)
|
||||
doesnt = Record.new(
|
||||
zone,
|
||||
'bad.compare',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'doesnt.example.com.'},
|
||||
)
|
||||
zone.add_record(doesnt)
|
||||
matches_many = Record.new(
|
||||
zone,
|
||||
'good.values',
|
||||
{
|
||||
'type': 'TXT',
|
||||
'ttl': 42,
|
||||
'values': ['matches.example.com.', 'another'],
|
||||
},
|
||||
)
|
||||
zone.add_record(matches_many)
|
||||
doesnt_many = Record.new(
|
||||
zone,
|
||||
'bad.values',
|
||||
{
|
||||
'type': 'TXT',
|
||||
'ttl': 42,
|
||||
'values': ['doesnt.example.com.', 'another'],
|
||||
},
|
||||
)
|
||||
zone.add_record(doesnt_many)
|
||||
matchable1 = Record.new(
|
||||
zone,
|
||||
'first.regex',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'start.f43ad96.end.'},
|
||||
)
|
||||
zone.add_record(matchable1)
|
||||
matchable2 = Record.new(
|
||||
zone,
|
||||
'second.regex',
|
||||
{'type': 'CNAME', 'ttl': 42, 'value': 'start.a3b444c.end.'},
|
||||
)
|
||||
zone.add_record(matchable2)
|
||||
|
||||
def test_exact(self):
|
||||
rejects = ValueRejectlistFilter('exact', ('matches.example.com.',))
|
||||
|
||||
self.assertEqual(6, len(self.zone.records))
|
||||
filtered = rejects.process_source_zone(self.zone.copy())
|
||||
self.assertEqual(4, len(filtered.records))
|
||||
self.assertEqual(
|
||||
['bad.compare', 'bad.values', 'first.regex', 'second.regex'],
|
||||
sorted([r.name for r in filtered.records]),
|
||||
)
|
||||
|
||||
def test_regex(self):
|
||||
rejects = ValueRejectlistFilter('exact', ('/^start\\..+\\.end\\.$/',))
|
||||
|
||||
self.assertEqual(6, len(self.zone.records))
|
||||
filtered = rejects.process_source_zone(self.zone.copy())
|
||||
self.assertEqual(4, len(filtered.records))
|
||||
self.assertEqual(
|
||||
['bad.compare', 'bad.values', 'good.compare', 'good.values'],
|
||||
sorted([r.name for r in filtered.records]),
|
||||
)
|
||||
|
||||
|
||||
class TestNetworkValueFilter(TestCase):
|
||||
zone = Zone('unit.tests.', [])
|
||||
for record in [
|
||||
|
||||
Reference in New Issue
Block a user