Browse Source

Merge remote-tracking branch 'origin/master' into process-desired-zone

pull/757/head
Ross McFarland 4 years ago
parent
commit
53a21b649a
No known key found for this signature in database GPG Key ID: 943B179E15D3B22A
12 changed files with 123 additions and 24 deletions
  1. +3
    -2
      octodns/provider/azuredns.py
  2. +18
    -1
      octodns/provider/base.py
  3. +18
    -7
      octodns/provider/ns1.py
  4. +1
    -0
      octodns/provider/yaml.py
  5. +26
    -2
      octodns/record/__init__.py
  6. +2
    -0
      octodns/source/base.py
  7. +1
    -1
      tests/config/split/unit.tests.tst/ptr.yaml
  8. +1
    -1
      tests/config/unit.tests.yaml
  9. +23
    -7
      tests/test_octodns_provider_azuredns.py
  10. +14
    -0
      tests/test_octodns_provider_base.py
  11. +15
    -2
      tests/test_octodns_provider_ns1.py
  12. +1
    -1
      tests/test_octodns_record.py

+ 3
- 2
octodns/provider/azuredns.py View File

@ -456,6 +456,7 @@ class AzureProvider(BaseProvider):
''' '''
SUPPORTS_GEO = False SUPPORTS_GEO = False
SUPPORTS_DYNAMIC = True SUPPORTS_DYNAMIC = True
SUPPORTS_MUTLIVALUE_PTR = True
SUPPORTS = set(('A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SRV', SUPPORTS = set(('A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SRV',
'TXT')) 'TXT'))
@ -707,8 +708,8 @@ class AzureProvider(BaseProvider):
return {'values': [_check_endswith_dot(val) for val in vals]} return {'values': [_check_endswith_dot(val) for val in vals]}
def _data_for_PTR(self, azrecord): def _data_for_PTR(self, azrecord):
ptrdname = azrecord.ptr_records[0].ptrdname
return {'value': _check_endswith_dot(ptrdname)}
vals = [ar.ptrdname for ar in azrecord.ptr_records]
return {'values': [_check_endswith_dot(val) for val in vals]}
def _data_for_SRV(self, azrecord): def _data_for_SRV(self, azrecord):
return {'values': [{'priority': ar.priority, 'weight': ar.weight, return {'values': [{'priority': ar.priority, 'weight': ar.weight,


+ 18
- 1
octodns/provider/base.py View File

@ -44,7 +44,23 @@ class BaseProvider(BaseSource):
that are made to have them logged or throw errors depending on the that are made to have them logged or throw errors depending on the
configuration configuration
''' '''
return desired
if self.SUPPORTS_MUTLIVALUE_PTR:
# nothing do here
return desired
new_desired = Zone(desired.name, desired.sub_zones)
for record in desired.records:
if record._type == 'PTR' and len(record.values) > 1:
# replace with a single-value copy
self.log.warn('does not support multi-value PTR records; '
'will use only %s for %s', record.value,
record.fqdn)
record = record.copy()
record.values = [record.value]
new_desired.add_record(record)
return new_desired
def _include_change(self, change): def _include_change(self, change):
''' '''
@ -69,6 +85,7 @@ class BaseProvider(BaseSource):
def plan(self, desired, processors=[]): def plan(self, desired, processors=[]):
self.log.info('plan: desired=%s', desired.name) self.log.info('plan: desired=%s', desired.name)
# process desired zone for any custom zone/record modification
desired = self._process_desired_zone(desired) desired = self._process_desired_zone(desired)
existing = Zone(desired.name, desired.sub_zones) existing = Zone(desired.name, desired.sub_zones)


+ 18
- 7
octodns/provider/ns1.py View File

@ -20,6 +20,10 @@ from ..record import Record, Update
from .base import BaseProvider from .base import BaseProvider
def _ensure_endswith_dot(string):
return string if string.endswith('.') else '{}.'.format(string)
class Ns1Exception(Exception): class Ns1Exception(Exception):
pass pass
@ -257,6 +261,7 @@ class Ns1Provider(BaseProvider):
''' '''
SUPPORTS_GEO = True SUPPORTS_GEO = True
SUPPORTS_DYNAMIC = True SUPPORTS_DYNAMIC = True
SUPPORTS_MUTLIVALUE_PTR = True
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'NAPTR', SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'NAPTR',
'NS', 'PTR', 'SPF', 'SRV', 'TXT', 'URLFWD')) 'NS', 'PTR', 'SPF', 'SRV', 'TXT', 'URLFWD'))
@ -720,7 +725,6 @@ class Ns1Provider(BaseProvider):
} }
_data_for_ALIAS = _data_for_CNAME _data_for_ALIAS = _data_for_CNAME
_data_for_PTR = _data_for_CNAME
def _data_for_MX(self, _type, record): def _data_for_MX(self, _type, record):
values = [] values = []
@ -759,10 +763,11 @@ class Ns1Provider(BaseProvider):
return { return {
'ttl': record['ttl'], 'ttl': record['ttl'],
'type': _type, 'type': _type,
'values': [a if a.endswith('.') else '{}.'.format(a)
for a in record['short_answers']],
'values': record['short_answers'],
} }
_data_for_PTR = _data_for_NS
def _data_for_SRV(self, _type, record): def _data_for_SRV(self, _type, record):
values = [] values = []
for answer in record['short_answers']: for answer in record['short_answers']:
@ -812,9 +817,10 @@ class Ns1Provider(BaseProvider):
for record in ns1_zone['records']: for record in ns1_zone['records']:
if record['type'] in ['ALIAS', 'CNAME', 'MX', 'NS', 'PTR', if record['type'] in ['ALIAS', 'CNAME', 'MX', 'NS', 'PTR',
'SRV']: 'SRV']:
for i, a in enumerate(record['short_answers']):
if not a.endswith('.'):
record['short_answers'][i] = '{}.'.format(a)
record['short_answers'] = [
_ensure_endswith_dot(a)
for a in record['short_answers']
]
if record.get('tier', 1) > 1: if record.get('tier', 1) > 1:
# Need to get the full record data for geo records # Need to get the full record data for geo records
@ -1304,7 +1310,6 @@ class Ns1Provider(BaseProvider):
return {'answers': [record.value], 'ttl': record.ttl}, None return {'answers': [record.value], 'ttl': record.ttl}, None
_params_for_ALIAS = _params_for_CNAME _params_for_ALIAS = _params_for_CNAME
_params_for_PTR = _params_for_CNAME
def _params_for_MX(self, record): def _params_for_MX(self, record):
values = [(v.preference, v.exchange) for v in record.values] values = [(v.preference, v.exchange) for v in record.values]
@ -1315,6 +1320,12 @@ class Ns1Provider(BaseProvider):
v.replacement) for v in record.values] v.replacement) for v in record.values]
return {'answers': values, 'ttl': record.ttl}, None return {'answers': values, 'ttl': record.ttl}, None
def _params_for_PTR(self, record):
return {
'answers': record.values,
'ttl': record.ttl,
}, None
def _params_for_SRV(self, record): def _params_for_SRV(self, record):
values = [(v.priority, v.weight, v.port, v.target) values = [(v.priority, v.weight, v.port, v.target)
for v in record.values] for v in record.values]


+ 1
- 0
octodns/provider/yaml.py View File

@ -104,6 +104,7 @@ class YamlProvider(BaseProvider):
''' '''
SUPPORTS_GEO = True SUPPORTS_GEO = True
SUPPORTS_DYNAMIC = True SUPPORTS_DYNAMIC = True
SUPPORTS_MUTLIVALUE_PTR = True
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'DNAME', 'LOC', 'MX', SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'DNAME', 'LOC', 'MX',
'NAPTR', 'NS', 'PTR', 'SSHFP', 'SPF', 'SRV', 'TXT', 'NAPTR', 'NS', 'PTR', 'SSHFP', 'SPF', 'SRV', 'TXT',
'URLFWD')) 'URLFWD'))


+ 26
- 2
octodns/record/__init__.py View File

@ -1256,13 +1256,37 @@ class NsRecord(_ValuesMixin, Record):
class PtrValue(_TargetValue): class PtrValue(_TargetValue):
pass
@classmethod
def validate(cls, values, _type):
if not isinstance(values, list):
values = [values]
reasons = []
if not values:
reasons.append('missing values')
for value in values:
reasons.extend(super(PtrValue, cls).validate(value, _type))
return reasons
@classmethod
def process(cls, values):
return [super(PtrValue, cls).process(v) for v in values]
class PtrRecord(_ValueMixin, Record):
class PtrRecord(_ValuesMixin, Record):
_type = 'PTR' _type = 'PTR'
_value_type = PtrValue _value_type = PtrValue
# This is for backward compatibility with providers that don't support
# multi-value PTR records.
@property
def value(self):
return self.values[0]
class SshfpValue(EqualityTupleMixin): class SshfpValue(EqualityTupleMixin):
VALID_ALGORITHMS = (1, 2, 3, 4) VALID_ALGORITHMS = (1, 2, 3, 4)


+ 2
- 0
octodns/source/base.py View File

@ -8,6 +8,8 @@ from __future__ import absolute_import, division, print_function, \
class BaseSource(object): class BaseSource(object):
SUPPORTS_MUTLIVALUE_PTR = False
def __init__(self, id): def __init__(self, id):
self.id = id self.id = id
if not getattr(self, 'log', False): if not getattr(self, 'log', False):


+ 1
- 1
tests/config/split/unit.tests.tst/ptr.yaml View File

@ -2,4 +2,4 @@
ptr: ptr:
ttl: 300 ttl: 300
type: PTR type: PTR
value: foo.bar.com.
values: [foo.bar.com.]

+ 1
- 1
tests/config/unit.tests.yaml View File

@ -152,7 +152,7 @@ naptr:
ptr: ptr:
ttl: 300 ttl: 300
type: PTR type: PTR
value: foo.bar.com.
values: [foo.bar.com.]
spf: spf:
ttl: 600 ttl: 600
type: SPF type: SPF


+ 23
- 7
tests/test_octodns_provider_azuredns.py View File

@ -150,6 +150,11 @@ octo_records.append(Record.new(zone, 'txt3', {
'type': 'TXT', 'type': 'TXT',
'values': ['txt multiple test', long_txt]})) 'values': ['txt multiple test', long_txt]}))
octo_records.append(Record.new(zone, 'ptr2', {
'ttl': 11,
'type': 'PTR',
'values': ['ptr21.unit.tests.', 'ptr22.unit.tests.']}))
azure_records = [] azure_records = []
_base0 = _AzureRecord('TestAzure', octo_records[0]) _base0 = _AzureRecord('TestAzure', octo_records[0])
_base0.zone_name = 'unit.tests' _base0.zone_name = 'unit.tests'
@ -338,6 +343,15 @@ _base18.params['txt_records'] = [TxtRecord(value=['txt multiple test']),
TxtRecord(value=[long_txt_az1, long_txt_az2])] TxtRecord(value=[long_txt_az1, long_txt_az2])]
azure_records.append(_base18) azure_records.append(_base18)
_base19 = _AzureRecord('TestAzure', octo_records[19])
_base19.zone_name = 'unit.tests'
_base19.relative_record_set_name = 'ptr2'
_base19.record_type = 'PTR'
_base19.params['ttl'] = 11
_base19.params['ptr_records'] = [PtrRecord(ptrdname='ptr21.unit.tests.'),
PtrRecord(ptrdname='ptr22.unit.tests.')]
azure_records.append(_base19)
class Test_AzureRecord(TestCase): class Test_AzureRecord(TestCase):
def test_azure_record(self): def test_azure_record(self):
@ -2054,15 +2068,16 @@ class TestAzureDnsProvider(TestCase):
def test_apply(self): def test_apply(self):
provider = self._get_provider() provider = self._get_provider()
half = int(len(octo_records) / 2)
expected_n = len(octo_records)
half = int(expected_n / 2)
changes = [Create(r) for r in octo_records[:half]] + \ changes = [Create(r) for r in octo_records[:half]] + \
[Update(r, r) for r in octo_records[half:]] [Update(r, r) for r in octo_records[half:]]
deletes = [Delete(r) for r in octo_records] deletes = [Delete(r) for r in octo_records]
self.assertEquals(19, provider.apply(Plan(None, zone,
changes, True)))
self.assertEquals(19, provider.apply(Plan(zone, zone,
deletes, True)))
self.assertEquals(expected_n, provider.apply(Plan(None, zone,
changes, True)))
self.assertEquals(expected_n, provider.apply(Plan(zone, zone,
deletes, True)))
def test_apply_create_dynamic(self): def test_apply_create_dynamic(self):
provider = self._get_provider() provider = self._get_provider()
@ -2320,8 +2335,9 @@ class TestAzureDnsProvider(TestCase):
_get = provider._dns_client.zones.get _get = provider._dns_client.zones.get
_get.side_effect = CloudError(Mock(status=404), err_msg) _get.side_effect = CloudError(Mock(status=404), err_msg)
self.assertEquals(19, provider.apply(Plan(None, desired, changes,
True)))
expected_n = len(octo_records)
self.assertEquals(expected_n, provider.apply(Plan(None, desired,
changes, True)))
def test_check_zone_no_create(self): def test_check_zone_no_create(self):
provider = self._get_provider() provider = self._get_provider()


+ 14
- 0
tests/test_octodns_provider_base.py View File

@ -231,6 +231,20 @@ class TestBaseProvider(TestCase):
# We filtered out the only change # We filtered out the only change
self.assertFalse(plan) self.assertFalse(plan)
def test_process_desired_zone(self):
zone1 = Zone('unit.tests.', [])
record1 = Record.new(zone1, 'ptr', {
'type': 'PTR',
'ttl': 3600,
'values': ['foo.com.', 'bar.com.'],
})
zone1.add_record(record1)
zone2 = HelperProvider('hasptr')._process_desired_zone(zone1)
record2 = list(zone2.records)[0]
self.assertEqual(len(record2.values), 1)
def test_safe_none(self): def test_safe_none(self):
# No changes is safe # No changes is safe
Plan(None, None, [], True).raise_if_unsafe() Plan(None, None, [], True).raise_if_unsafe()


+ 15
- 2
tests/test_octodns_provider_ns1.py View File

@ -120,6 +120,11 @@ class TestNs1Provider(TestCase):
'query': 0, 'query': 0,
}, },
})) }))
expected.add(Record.new(zone, '1.2.3.4', {
'ttl': 42,
'type': 'PTR',
'values': ['one.one.one.one.', 'two.two.two.two.'],
}))
ns1_records = [{ ns1_records = [{
'type': 'A', 'type': 'A',
@ -180,6 +185,11 @@ class TestNs1Provider(TestCase):
'ttl': 41, 'ttl': 41,
'short_answers': ['/ http://foo.unit.tests 301 2 0'], 'short_answers': ['/ http://foo.unit.tests 301 2 0'],
'domain': 'urlfwd.unit.tests.', 'domain': 'urlfwd.unit.tests.',
}, {
'type': 'PTR',
'ttl': 42,
'short_answers': ['one.one.one.one.', 'two.two.two.two.'],
'domain': '1.2.3.4.unit.tests.',
}] }]
@patch('ns1.rest.records.Records.retrieve') @patch('ns1.rest.records.Records.retrieve')
@ -358,10 +368,10 @@ class TestNs1Provider(TestCase):
ResourceException('server error: zone not found') ResourceException('server error: zone not found')
zone_create_mock.side_effect = ['foo'] zone_create_mock.side_effect = ['foo']
# Test out the create rate-limit handling, then 9 successes
# Test out the create rate-limit handling, then successes for the rest
record_create_mock.side_effect = [ record_create_mock.side_effect = [
RateLimitException('boo', period=0), RateLimitException('boo', period=0),
] + ([None] * 10)
] + ([None] * len(self.expected))
got_n = provider.apply(plan) got_n = provider.apply(plan)
self.assertEquals(expected_n, got_n) self.assertEquals(expected_n, got_n)
@ -379,6 +389,9 @@ class TestNs1Provider(TestCase):
call('unit.tests', 'unit.tests', 'MX', answers=[ call('unit.tests', 'unit.tests', 'MX', answers=[
(10, 'mx1.unit.tests.'), (20, 'mx2.unit.tests.') (10, 'mx1.unit.tests.'), (20, 'mx2.unit.tests.')
], ttl=35), ], ttl=35),
call('unit.tests', '1.2.3.4.unit.tests', 'PTR', answers=[
'one.one.one.one.', 'two.two.two.two.',
], ttl=42),
]) ])
# Update & delete # Update & delete


+ 1
- 1
tests/test_octodns_record.py View File

@ -2733,7 +2733,7 @@ class TestRecordValidation(TestCase):
'type': 'PTR', 'type': 'PTR',
'ttl': 600, 'ttl': 600,
}) })
self.assertEquals(['missing value'], ctx.exception.reasons)
self.assertEquals(['missing values'], ctx.exception.reasons)
# not a valid FQDN # not a valid FQDN
with self.assertRaises(ValidationError) as ctx: with self.assertRaises(ValidationError) as ctx:


Loading…
Cancel
Save