From 9ff5942d1934a693b0e8bc4f5d71ef2a48659ed8 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 1 Sep 2018 13:31:53 +1000 Subject: [PATCH] Add: Ability to manage "proxied" flag of "A", "AAAA" and "CNAME" records via YAML configuration (see "CloudflareProvider" class docstring for details). --- octodns/provider/cloudflare.py | 57 ++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/octodns/provider/cloudflare.py b/octodns/provider/cloudflare.py index 50e4b90..a418169 100644 --- a/octodns/provider/cloudflare.py +++ b/octodns/provider/cloudflare.py @@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, \ unicode_literals from collections import defaultdict +from copy import deepcopy from logging import getLogger from requests import Session @@ -27,6 +28,9 @@ class CloudflareAuthenticationError(CloudflareError): CloudflareError.__init__(self, data) +_PROXIABLE_RECORD_TYPES = {'A', 'AAAA', 'CNAME'} + + class CloudflareProvider(BaseProvider): ''' Cloudflare DNS provider @@ -221,7 +225,14 @@ class CloudflareProvider(BaseProvider): data_for = getattr(self, '_data_for_{}'.format(_type)) data = data_for(_type, records) - return Record.new(zone, name, data, source=self, lenient=lenient) + record = Record.new(zone, name, data, source=self, lenient=lenient) + + if _type in _PROXIABLE_RECORD_TYPES: + record._octodns['cloudflare'] = { + 'proxied': records[0].get('proxied', False) + } + + return record def populate(self, zone, target=False, lenient=False): self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, @@ -260,8 +271,18 @@ class CloudflareProvider(BaseProvider): def _include_change(self, change): if isinstance(change, Update): - existing = change.existing.data new = change.new.data + + # Cloudflare manages TTL of proxied records, so we should exclude + # TTL from the comparison (to prevent false-positives). + if self._record_is_proxied(change.existing): + existing = deepcopy(change.existing.data) + existing.update({ + 'ttl': new['ttl'] + }) + else: + existing = change.existing.data + new['ttl'] = max(self.MIN_TTL, new['ttl']) if new == existing: return False @@ -322,6 +343,12 @@ class CloudflareProvider(BaseProvider): } } + def _record_is_proxied(self, record): + return ( + not self.cdn and + record._octodns.get('cloudflare', {}).get('proxied', False) + ) + def _gen_data(self, record): name = record.fqdn[:-1] _type = record._type @@ -338,6 +365,12 @@ class CloudflareProvider(BaseProvider): 'type': _type, 'ttl': ttl, }) + + if _type in _PROXIABLE_RECORD_TYPES: + content.update({ + 'proxied': self._record_is_proxied(record) + }) + yield content def _gen_key(self, data): @@ -512,3 +545,23 @@ class CloudflareProvider(BaseProvider): # clear the cache self._zone_records.pop(name, None) + + def _extra_changes(self, existing, desired, changes): + extra_changes = [] + + existing_records = {r: r for r in existing.records} + changed_records = {c.record for c in changes} + + for desired_record in desired.records: + if desired_record not in existing.records: # Will be created + continue + elif desired_record in changed_records: # Already being updated + continue + + existing_record = existing_records[desired_record] + + if (self._record_is_proxied(existing_record) != + self._record_is_proxied(desired_record)): + extra_changes.append(Update(existing_record, desired_record)) + + return extra_changes