Browse Source

Multi-value PTR records

pull/754/head
Viranch Mehta 4 years ago
parent
commit
49bff426b7
No known key found for this signature in database GPG Key ID: D83D1392AE9F93B4
7 changed files with 76 additions and 16 deletions
  1. +2
    -2
      octodns/provider/azuredns.py
  2. +17
    -7
      octodns/provider/ns1.py
  3. +29
    -2
      octodns/record/__init__.py
  4. +1
    -1
      tests/config/split/unit.tests.tst/ptr.yaml
  5. +1
    -1
      tests/config/unit.tests.yaml
  6. +15
    -2
      tests/test_octodns_provider_ns1.py
  7. +11
    -1
      tests/test_octodns_record.py

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

@ -707,8 +707,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,


+ 17
- 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 _check_endswith_dot(string):
return string if string.endswith('.') else '{}.'.format(string)
class Ns1Exception(Exception): class Ns1Exception(Exception):
pass pass
@ -717,7 +721,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 = []
@ -756,10 +759,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']:
@ -809,9 +813,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'] = [
_check_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
@ -1297,7 +1302,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]
@ -1308,6 +1312,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]


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

@ -1256,13 +1256,40 @@ 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
class PtrRecord(_ValueMixin, Record):
@classmethod
def process(cls, values):
return [super(PtrValue, cls).process(v) for v in values]
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):
if len(self.values) == 1:
return self.values[0]
raise AttributeError("Multi-value PTR record has no attribute 'value'")
class SshfpValue(EqualityTupleMixin): class SshfpValue(EqualityTupleMixin):
VALID_ALGORITHMS = (1, 2, 3, 4) VALID_ALGORITHMS = (1, 2, 3, 4)


+ 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


+ 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


+ 11
- 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:
@ -2755,6 +2755,16 @@ class TestRecordValidation(TestCase):
self.assertEquals(['PTR value "foo.bar" missing trailing .'], self.assertEquals(['PTR value "foo.bar" missing trailing .'],
ctx.exception.reasons) ctx.exception.reasons)
# multi-value requesting single-value
with self.assertRaises(AttributeError) as ctx:
Record.new(self.zone, '', {
'type': 'PTR',
'ttl': 600,
'values': ['foo.com.', 'bar.net.'],
}).value
self.assertEquals("Multi-value PTR record has no attribute 'value'",
text_type(ctx.exception))
def test_SSHFP(self): def test_SSHFP(self):
# doesn't blow up # doesn't blow up
Record.new(self.zone, '', { Record.new(self.zone, '', {


Loading…
Cancel
Save