Browse Source

Add Plan.meta support for making non-record changes to zones

pull/1236/head
Ross McFarland 10 months ago
parent
commit
4c48609848
No known key found for this signature in database GPG Key ID: 943B179E15D3B22A
4 changed files with 51 additions and 30 deletions
  1. +2
    -0
      CHANGELOG.md
  2. +16
    -3
      octodns/provider/base.py
  3. +27
    -22
      octodns/provider/plan.py
  4. +6
    -5
      tests/test_octodns_plan.py

+ 2
- 0
CHANGELOG.md View File

@ -8,6 +8,8 @@
the default when enforce_order=True and simple `sort`.
* Fix type-o in _build_kwargs handler notification
* Add support for configuring OwnershipProcessor TXT record's TTL
* Add Plan.meta to allow providers to indicate they need to make changes to the
zone that are not record specific
## v1.10.0 - 2024-10-06 - Lots of little stuff


+ 16
- 3
octodns/provider/base.py View File

@ -214,6 +214,14 @@ class BaseProvider(BaseSource):
'''
return []
def _plan_meta(self, existing, desired, changes):
'''
An opportunity for providers to indicate they have "meta" changes
to the zone which are unrelated to records. Examples may include servive
plan changes, replication settings, and notes.
'''
return None
def supports_warn_or_except(self, msg, fallback):
if self.strict_supports:
raise SupportsException(f'{self.id}: {msg}')
@ -269,14 +277,19 @@ class BaseProvider(BaseSource):
)
changes += extra
if changes:
meta = self._plan_meta(
existing=existing, desired=desired, changes=changes
)
if changes or meta:
plan = Plan(
existing,
desired,
changes,
exists,
self.update_pcent_threshold,
self.delete_pcent_threshold,
update_pcent_threshold=self.update_pcent_threshold,
delete_pcent_threshold=self.delete_pcent_threshold,
meta=meta,
)
self.log.info('plan: %s', plan)
return plan


+ 27
- 22
octodns/provider/plan.py View File

@ -6,6 +6,7 @@ from collections import defaultdict
from io import StringIO
from json import dumps
from logging import DEBUG, ERROR, INFO, WARN, getLogger
from pprint import pformat
from sys import stdout
@ -50,6 +51,7 @@ class Plan(object):
exists,
update_pcent_threshold=MAX_SAFE_UPDATE_PCENT,
delete_pcent_threshold=MAX_SAFE_DELETE_PCENT,
meta=None,
):
self.existing = existing
self.desired = desired
@ -59,6 +61,7 @@ class Plan(object):
# them and/or is as safe as possible.
self.changes = sorted(changes)
self.exists = exists
self.meta = meta
# Zone thresholds take precedence over provider
if existing and existing.update_pcent_threshold is not None:
@ -74,22 +77,11 @@ class Plan(object):
change_counts[change.__class__.__name__] += 1
self.change_counts = change_counts
try:
existing_n = len(self.existing.records)
except AttributeError:
existing_n = 0
self.log.debug(
'__init__: Creates=%d, Updates=%d, Deletes=%d Existing=%d',
self.change_counts['Create'],
self.change_counts['Update'],
self.change_counts['Delete'],
existing_n,
)
self.log.debug('__init__: %s', self.__repr__())
@property
def data(self):
return {'changes': [c.data for c in self.changes]}
return {'changes': [c.data for c in self.changes], 'meta': self.meta}
def raise_if_unsafe(self):
if (
@ -140,11 +132,12 @@ class Plan(object):
creates = self.change_counts['Create']
updates = self.change_counts['Update']
deletes = self.change_counts['Delete']
existing = len(self.existing.records)
return (
f'Creates={creates}, Updates={updates}, Deletes={deletes}, '
f'Existing Records={existing}'
)
try:
existing = len(self.existing.records)
except AttributeError:
existing = 0
meta = self.meta is not None
return f'Creates={creates}, Updates={updates}, Deletes={deletes}, Existing={existing}, Meta={meta}'
class _PlanOutput(object):
@ -167,10 +160,7 @@ class PlanLogger(_PlanOutput):
raise Exception(f'Unsupported level: {level}')
def run(self, log, plans, *args, **kwargs):
hr = (
'*************************************************************'
'*******************\n'
)
hr = '********************************************************************************\n'
buf = StringIO()
buf.write('\n')
if plans:
@ -199,6 +189,11 @@ class PlanLogger(_PlanOutput):
buf.write(change.__repr__(leader='* '))
buf.write('\n* ')
if plan.meta:
buf.write('Meta: \n')
buf.write(pformat(plan.meta, indent=2, sort_dicts=True))
buf.write('\n')
buf.write('Summary: ')
buf.write(str(plan))
buf.write('\n')
@ -291,6 +286,11 @@ class PlanMarkdown(_PlanOutput):
fh.write(new.source.id)
fh.write(' |\n')
if plan.meta:
fh.write('\nMeta: ')
fh.write(pformat(plan.meta, indent=2, sort_dicts=True))
fh.write('\n')
fh.write('\nSummary: ')
fh.write(str(plan))
fh.write('\n\n')
@ -361,6 +361,11 @@ class PlanHtml(_PlanOutput):
fh.write(new.source.id)
fh.write('</td>\n </tr>\n')
if plan.meta:
fh.write(' <tr>\n <td colspan=6>Meta: ')
fh.write(pformat(plan.meta, indent=2, sort_dicts=True))
fh.write('</td>\n </tr>\n</table>\n')
fh.write(' <tr>\n <td colspan=6>Summary: ')
fh.write(str(plan))
fh.write('</td>\n </tr>\n</table>\n')


+ 6
- 5
tests/test_octodns_plan.py View File

@ -64,6 +64,7 @@ changes = [create, create2, delete, update]
plans = [
(simple, Plan(zone, zone, changes, True)),
(simple, Plan(zone, zone, changes, False)),
(simple, Plan(zone, zone, changes, False, meta={'key': 'val'})),
]
@ -105,8 +106,8 @@ class TestPlanLogger(TestCase):
PlanLogger('logger').run(log, plans)
out = log.out.getvalue()
self.assertTrue(
'Summary: Creates=2, Updates=1, '
'Deletes=1, Existing Records=0' in out
'Summary: Creates=2, Updates=1, Deletes=1, Existing=0, Meta=False'
in out
)
@ -123,8 +124,8 @@ class TestPlanHtml(TestCase):
PlanHtml('html').run(plans, fh=out)
out = out.getvalue()
self.assertTrue(
' <td colspan=6>Summary: Creates=2, Updates=1, '
'Deletes=1, Existing Records=0</td>' in out
' <td colspan=6>Summary: Creates=2, Updates=1, Deletes=1, Existing=0, Meta=False</td>'
in out
)
@ -398,7 +399,7 @@ class TestPlanSafety(TestCase):
def test_data(self):
data = plans[0][1].data
# plans should have a single key, changes
self.assertEqual(('changes',), tuple(data.keys()))
self.assertEqual(('changes', 'meta'), tuple(data.keys()))
# it should be a list
self.assertIsInstance(data['changes'], list)
# w/4 elements


Loading…
Cancel
Save