mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Sketch at process: source, target, plan setup, with ownership
This commit is contained in:
@@ -255,7 +255,6 @@ class Manager(object):
|
|||||||
for _, records in desired._records.items():
|
for _, records in desired._records.items():
|
||||||
for record in records:
|
for record in records:
|
||||||
zone.add_record(record.copy(zone=zone), lenient=lenient)
|
zone.add_record(record.copy(zone=zone), lenient=lenient)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for source in sources:
|
for source in sources:
|
||||||
try:
|
try:
|
||||||
@@ -267,9 +266,8 @@ class Manager(object):
|
|||||||
'param', source.__class__.__name__)
|
'param', source.__class__.__name__)
|
||||||
source.populate(zone)
|
source.populate(zone)
|
||||||
|
|
||||||
self.log.debug('sync: processing, zone=%s', zone_name)
|
|
||||||
for processor in processors:
|
for processor in processors:
|
||||||
zone = processor.process(zone)
|
zone = processor.process_source_zone(zone, sources=sources)
|
||||||
|
|
||||||
self.log.debug('sync: planning, zone=%s', zone_name)
|
self.log.debug('sync: planning, zone=%s', zone_name)
|
||||||
plans = []
|
plans = []
|
||||||
@@ -285,6 +283,9 @@ class Manager(object):
|
|||||||
# TODO: if someone has overrriden plan already this will be a
|
# TODO: if someone has overrriden plan already this will be a
|
||||||
# breaking change so we probably need to try both ways
|
# breaking change so we probably need to try both ways
|
||||||
plan = target.plan(zone, processors=processors)
|
plan = target.plan(zone, processors=processors)
|
||||||
|
for processor in processors:
|
||||||
|
plan = processor.process_plan(plan, sources=sources,
|
||||||
|
target=target)
|
||||||
if plan:
|
if plan:
|
||||||
plans.append((target, plan))
|
plans.append((target, plan))
|
||||||
|
|
||||||
|
|||||||
@@ -15,3 +15,16 @@ class BaseProcessor(object):
|
|||||||
|
|
||||||
def _create_zone(self, zone):
|
def _create_zone(self, zone):
|
||||||
return Zone(zone.name, sub_zones=zone.sub_zones)
|
return Zone(zone.name, sub_zones=zone.sub_zones)
|
||||||
|
|
||||||
|
def process_source_zone(self, zone, sources):
|
||||||
|
# sources may be empty, as will be the case for aliased zones
|
||||||
|
return zone
|
||||||
|
|
||||||
|
def process_target_zone(self, zone, target):
|
||||||
|
return zone
|
||||||
|
|
||||||
|
def process_plan(self, plan, sources, target):
|
||||||
|
# plan may be None if no changes were detected up until now, the
|
||||||
|
# process may still create a plan.
|
||||||
|
# sources may be empty, as will be the case for aliased zones
|
||||||
|
return plan
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class TypeAllowlistFilter(BaseProcessor):
|
|||||||
super(TypeAllowlistFilter, self).__init__(name)
|
super(TypeAllowlistFilter, self).__init__(name)
|
||||||
self.allowlist = allowlist
|
self.allowlist = allowlist
|
||||||
|
|
||||||
def process(self, zone, target=False):
|
def _process(self, zone, *args, **kwargs):
|
||||||
ret = self._create_zone(zone)
|
ret = self._create_zone(zone)
|
||||||
for record in zone.records:
|
for record in zone.records:
|
||||||
if record._type in self.allowlist:
|
if record._type in self.allowlist:
|
||||||
@@ -22,6 +22,9 @@ class TypeAllowlistFilter(BaseProcessor):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
process_source_zone = _process
|
||||||
|
process_target_zone = _process
|
||||||
|
|
||||||
|
|
||||||
class TypeRejectlistFilter(BaseProcessor):
|
class TypeRejectlistFilter(BaseProcessor):
|
||||||
|
|
||||||
@@ -29,10 +32,13 @@ class TypeRejectlistFilter(BaseProcessor):
|
|||||||
super(TypeRejectlistFilter, self).__init__(name)
|
super(TypeRejectlistFilter, self).__init__(name)
|
||||||
self.rejectlist = rejectlist
|
self.rejectlist = rejectlist
|
||||||
|
|
||||||
def process(self, zone, target=False):
|
def _process(self, zone, *args, **kwargs):
|
||||||
ret = self._create_zone(zone)
|
ret = self._create_zone(zone)
|
||||||
for record in zone.records:
|
for record in zone.records:
|
||||||
if record._type not in self.rejectlist:
|
if record._type not in self.rejectlist:
|
||||||
ret.add_record(record)
|
ret.add_record(record)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
process_source_zone = _process
|
||||||
|
process_target_zone = _process
|
||||||
|
|||||||
@@ -5,36 +5,111 @@
|
|||||||
from __future__ import absolute_import, division, print_function, \
|
from __future__ import absolute_import, division, print_function, \
|
||||||
unicode_literals
|
unicode_literals
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from ..provider.plan import Plan
|
||||||
from ..record import Record
|
from ..record import Record
|
||||||
|
|
||||||
from . import BaseProcessor
|
from . import BaseProcessor
|
||||||
|
|
||||||
|
|
||||||
|
# Mark anything octoDNS is managing that way it can know it's safe to modify or
|
||||||
|
# delete. We'll take ownership of existing records that we're told to manage
|
||||||
|
# and thus "own" them going forward.
|
||||||
class OwnershipProcessor(BaseProcessor):
|
class OwnershipProcessor(BaseProcessor):
|
||||||
|
|
||||||
def __init__(self, name, txt_name='_owner'):
|
def __init__(self, name, txt_name='_owner', txt_value='*octodns*'):
|
||||||
super(OwnershipProcessor, self).__init__(name)
|
super(OwnershipProcessor, self).__init__(name)
|
||||||
self.txt_name = txt_name
|
self.txt_name = txt_name
|
||||||
|
self.txt_value = txt_value
|
||||||
|
self._txt_values = [txt_value]
|
||||||
|
|
||||||
def add_ownerships(self, zone):
|
def process_source_zone(self, zone, *args, **kwargs):
|
||||||
ret = self._create_zone(zone)
|
ret = self._create_zone(zone)
|
||||||
for record in zone.records:
|
for record in zone.records:
|
||||||
|
# Always copy over the source records
|
||||||
ret.add_record(record)
|
ret.add_record(record)
|
||||||
name = '{}.{}.{}'.format(self.txt_name, record._type, record.name),
|
# Then create and add an ownership TXT for each of them
|
||||||
|
record_name = record.name.replace('*', '_wildcard')
|
||||||
|
if record.name:
|
||||||
|
name = '{}.{}.{}'.format(self.txt_name, record._type,
|
||||||
|
record_name)
|
||||||
|
else:
|
||||||
|
name = '{}.{}'.format(self.txt_name, record._type)
|
||||||
txt = Record.new(zone, name, {
|
txt = Record.new(zone, name, {
|
||||||
'type': 'TXT',
|
'type': 'TXT',
|
||||||
'ttl': 60,
|
'ttl': 60,
|
||||||
'value': 'octodns',
|
'value': self.txt_value,
|
||||||
})
|
})
|
||||||
ret.add_record(txt)
|
ret.add_record(txt)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def remove_unowned(self, zone):
|
def _is_ownership(self, record):
|
||||||
ret = self._create_zone(zone)
|
return record._type == 'TXT' and \
|
||||||
return ret
|
record.name.startswith(self.txt_name) \
|
||||||
|
and record.values == self._txt_values
|
||||||
|
|
||||||
def process(self, zone, target=False):
|
def process_plan(self, plan, *args, **kwargs):
|
||||||
if target:
|
if not plan:
|
||||||
return self.remove_unowned(zone)
|
# If we don't have any change there's nothing to do
|
||||||
return self.add_ownerships(zone)
|
return plan
|
||||||
|
|
||||||
|
# First find all the ownership info
|
||||||
|
owned = defaultdict(dict)
|
||||||
|
# We need to look for ownership in both the desired and existing
|
||||||
|
# states, many things will show up in both, but that's fine.
|
||||||
|
for record in list(plan.existing.records) + list(plan.desired.records):
|
||||||
|
if self._is_ownership(record):
|
||||||
|
pieces = record.name.split('.', 2)
|
||||||
|
if len(pieces) > 2:
|
||||||
|
_, _type, name = pieces
|
||||||
|
else:
|
||||||
|
_type = pieces[1]
|
||||||
|
name = ''
|
||||||
|
name = name.replace('_wildcard', '*')
|
||||||
|
owned[name][_type.upper()] = True
|
||||||
|
|
||||||
|
pprint(dict(owned))
|
||||||
|
|
||||||
|
# Cases:
|
||||||
|
# - Configured in source
|
||||||
|
# - We'll fully CRU/manage it adding ownership TXT,
|
||||||
|
# thanks to process_source_zone, if needed
|
||||||
|
# - Not in source
|
||||||
|
# - Has an ownership TXT - delete it & the ownership TXT
|
||||||
|
# - Does not have an ownership TXT - don't delete it
|
||||||
|
# - Special records like octodns-meta
|
||||||
|
# - Should be left alone and should not have ownerthis TXTs
|
||||||
|
|
||||||
|
pprint(plan.changes)
|
||||||
|
|
||||||
|
filtered_changes = []
|
||||||
|
for change in plan.changes:
|
||||||
|
record = change.record
|
||||||
|
|
||||||
|
pprint([change,
|
||||||
|
not self._is_ownership(record),
|
||||||
|
record._type not in owned[record.name],
|
||||||
|
record.name != 'octodns-meta'])
|
||||||
|
|
||||||
|
if not self._is_ownership(record) and \
|
||||||
|
record._type not in owned[record.name] and \
|
||||||
|
record.name != 'octodns-meta':
|
||||||
|
# It's not an ownership TXT, it's not owned, and it's not
|
||||||
|
# special we're going to ignore it
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We own this record or owned it up until now so whatever the
|
||||||
|
# change is we should do
|
||||||
|
filtered_changes.append(change)
|
||||||
|
|
||||||
|
pprint(filtered_changes)
|
||||||
|
|
||||||
|
if plan.changes != filtered_changes:
|
||||||
|
return Plan(plan.existing, plan.desired, filtered_changes,
|
||||||
|
plan.exists, plan.update_pcent_threshold,
|
||||||
|
plan.delete_pcent_threshold)
|
||||||
|
|
||||||
|
return plan
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class BaseProvider(BaseSource):
|
|||||||
'exists', self.id)
|
'exists', self.id)
|
||||||
|
|
||||||
for processor in processors:
|
for processor in processors:
|
||||||
existing = processor.process(existing, target=True)
|
existing = processor.process_target_zone(existing, target=self)
|
||||||
|
|
||||||
# compute the changes at the zone/record level
|
# compute the changes at the zone/record level
|
||||||
changes = existing.changes(desired, self)
|
changes = existing.changes(desired, self)
|
||||||
|
|||||||
Reference in New Issue
Block a user