1
0
mirror of https://github.com/github/octodns.git synced 2024-05-11 05:55:00 +00:00

Merge pull request #620 from Inikup/dname-records

Add support for DNAME records
This commit is contained in:
Ross McFarland
2020-10-27 13:22:23 -07:00
committed by GitHub
14 changed files with 94 additions and 31 deletions

View File

@@ -7,6 +7,7 @@ OctoDNS supports the following record types:
* `A`
* `AAAA`
* `CNAME`
* `DNAME`
* `MX`
* `NAPTR`
* `NS`

View File

@@ -104,8 +104,8 @@ class YamlProvider(BaseProvider):
'''
SUPPORTS_GEO = True
SUPPORTS_DYNAMIC = True
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'NAPTR', 'NS',
'PTR', 'SSHFP', 'SPF', 'SRV', 'TXT'))
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'DNAME', 'MX',
'NAPTR', 'NS', 'PTR', 'SSHFP', 'SPF', 'SRV', 'TXT'))
def __init__(self, id, directory, default_ttl=3600, enforce_order=True,
populate_should_replace=False, *args, **kwargs):

View File

@@ -95,6 +95,7 @@ class Record(EqualityTupleMixin):
'ALIAS': AliasRecord,
'CAA': CaaRecord,
'CNAME': CnameRecord,
'DNAME': DnameRecord,
'MX': MxRecord,
'NAPTR': NaptrRecord,
'NS': NsRecord,
@@ -759,6 +760,10 @@ class CnameValue(_TargetValue):
pass
class DnameValue(_TargetValue):
pass
class ARecord(_DynamicMixin, _GeoMixin, Record):
_type = 'A'
_value_type = Ipv4List
@@ -842,6 +847,11 @@ class CnameRecord(_DynamicMixin, _ValueMixin, Record):
return reasons
class DnameRecord(_DynamicMixin, _ValueMixin, Record):
_type = 'DNAME'
_value_type = DnameValue
class MxValue(EqualityTupleMixin):
@classmethod

View File

@@ -0,0 +1,5 @@
---
dname:
ttl: 300
type: DNAME
value: unit.tests.

View File

@@ -56,6 +56,10 @@ cname:
ttl: 300
type: CNAME
value: unit.tests.
dname:
ttl: 300
type: DNAME
value: unit.tests.
excluded:
octodns:
excluded:

View File

@@ -118,12 +118,12 @@ class TestManager(TestCase):
environ['YAML_TMP_DIR'] = tmpdir.dirname
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False)
self.assertEquals(21, tc)
self.assertEquals(22, tc)
# try with just one of the zones
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, eligible_zones=['unit.tests.'])
self.assertEquals(15, tc)
self.assertEquals(16, tc)
# the subzone, with 2 targets
tc = Manager(get_config_filename('simple.yaml')) \
@@ -138,18 +138,18 @@ class TestManager(TestCase):
# Again with force
tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, force=True)
self.assertEquals(21, tc)
self.assertEquals(22, tc)
# Again with max_workers = 1
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
.sync(dry_run=False, force=True)
self.assertEquals(21, tc)
self.assertEquals(22, tc)
# Include meta
tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
include_meta=True) \
.sync(dry_run=False, force=True)
self.assertEquals(25, tc)
self.assertEquals(26, tc)
def test_eligible_sources(self):
with TemporaryDirectory() as tmpdir:
@@ -183,13 +183,13 @@ class TestManager(TestCase):
fh.write('---\n{}')
changes = manager.compare(['in'], ['dump'], 'unit.tests.')
self.assertEquals(15, len(changes))
self.assertEquals(16, len(changes))
# Compound sources with varying support
changes = manager.compare(['in', 'nosshfp'],
['dump'],
'unit.tests.')
self.assertEquals(14, len(changes))
self.assertEquals(15, len(changes))
with self.assertRaises(ManagerException) as ctx:
manager.compare(['nope'], ['dump'], 'unit.tests.')

View File

@@ -138,7 +138,7 @@ class TestConstellixProvider(TestCase):
plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 5
n = len(self.expected.records) - 6
self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan))

View File

@@ -163,7 +163,7 @@ class TestDigitalOceanProvider(TestCase):
plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 7
n = len(self.expected.records) - 8
self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan))
self.assertFalse(plan.exists)

View File

@@ -137,7 +137,7 @@ class TestDnsimpleProvider(TestCase):
plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded
n = len(self.expected.records) - 3
n = len(self.expected.records) - 4
self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan))
self.assertFalse(plan.exists)

View File

@@ -140,7 +140,7 @@ class TestDnsMadeEasyProvider(TestCase):
plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 5
n = len(self.expected.records) - 6
self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan))

View File

@@ -374,7 +374,7 @@ class TestEasyDNSProvider(TestCase):
plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 6
n = len(self.expected.records) - 7
self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan))
self.assertFalse(plan.exists)

View File

@@ -171,7 +171,7 @@ class TestPowerDnsProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
expected_n = len(expected.records) - 2
expected_n = len(expected.records) - 3
self.assertEquals(16, expected_n)
# No diffs == no changes
@@ -277,7 +277,7 @@ class TestPowerDnsProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
self.assertEquals(18, len(expected.records))
self.assertEquals(19, len(expected.records))
# A small change to a single record
with requests_mock() as mock:

View File

@@ -35,7 +35,7 @@ class TestYamlProvider(TestCase):
# without it we see everything
source.populate(zone)
self.assertEquals(18, len(zone.records))
self.assertEquals(19, len(zone.records))
source.populate(dynamic_zone)
self.assertEquals(5, len(dynamic_zone.records))
@@ -58,12 +58,12 @@ class TestYamlProvider(TestCase):
# We add everything
plan = target.plan(zone)
self.assertEquals(15, len([c for c in plan.changes
self.assertEquals(16, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertFalse(isfile(yaml_file))
# Now actually do it
self.assertEquals(15, target.apply(plan))
self.assertEquals(16, target.apply(plan))
self.assertTrue(isfile(yaml_file))
# Dynamic plan
@@ -87,7 +87,7 @@ class TestYamlProvider(TestCase):
# A 2nd sync should still create everything
plan = target.plan(zone)
self.assertEquals(15, len([c for c in plan.changes
self.assertEquals(16, len([c for c in plan.changes
if isinstance(c, Create)]))
with open(yaml_file) as fh:
@@ -109,6 +109,7 @@ class TestYamlProvider(TestCase):
# these are stored as singular 'value'
self.assertTrue('value' in data.pop('aaaa'))
self.assertTrue('value' in data.pop('cname'))
self.assertTrue('value' in data.pop('dname'))
self.assertTrue('value' in data.pop('included'))
self.assertTrue('value' in data.pop('ptr'))
self.assertTrue('value' in data.pop('spf'))
@@ -237,7 +238,7 @@ class TestSplitYamlProvider(TestCase):
# without it we see everything
source.populate(zone)
self.assertEquals(18, len(zone.records))
self.assertEquals(19, len(zone.records))
source.populate(dynamic_zone)
self.assertEquals(5, len(dynamic_zone.records))
@@ -251,12 +252,12 @@ class TestSplitYamlProvider(TestCase):
# We add everything
plan = target.plan(zone)
self.assertEquals(15, len([c for c in plan.changes
self.assertEquals(16, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertFalse(isdir(zone_dir))
# Now actually do it
self.assertEquals(15, target.apply(plan))
self.assertEquals(16, target.apply(plan))
# Dynamic plan
plan = target.plan(dynamic_zone)
@@ -279,7 +280,7 @@ class TestSplitYamlProvider(TestCase):
# A 2nd sync should still create everything
plan = target.plan(zone)
self.assertEquals(15, len([c for c in plan.changes
self.assertEquals(16, len([c for c in plan.changes
if isinstance(c, Create)]))
yaml_file = join(zone_dir, '$unit.tests.yaml')
@@ -302,8 +303,8 @@ class TestSplitYamlProvider(TestCase):
self.assertTrue('values' in data.pop(record_name))
# These are stored as singular "value." Again, check each file.
for record_name in ('aaaa', 'cname', 'included', 'ptr', 'spf',
'www.sub', 'www'):
for record_name in ('aaaa', 'cname', 'dname', 'included', 'ptr',
'spf', 'www.sub', 'www'):
yaml_file = join(zone_dir, '{}.yaml'.format(record_name))
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
@@ -387,7 +388,7 @@ class TestOverridingYamlProvider(TestCase):
base.populate(zone)
got = {r.name: r for r in zone.records}
self.assertEquals(5, len(got))
# We get the "dynamic" A from the bae config
# We get the "dynamic" A from the base config
self.assertTrue('dynamic' in got['a'].data)
# No added
self.assertFalse('added' in got)

View File

@@ -9,10 +9,10 @@ from six import text_type
from unittest import TestCase
from octodns.record import ARecord, AaaaRecord, AliasRecord, CaaRecord, \
CaaValue, CnameRecord, Create, Delete, GeoValue, MxRecord, MxValue, \
NaptrRecord, NaptrValue, NsRecord, PtrRecord, Record, SshfpRecord, \
SshfpValue, SpfRecord, SrvRecord, SrvValue, TxtRecord, Update, \
ValidationError, _Dynamic, _DynamicPool, _DynamicRule
CaaValue, CnameRecord, DnameRecord, Create, Delete, GeoValue, MxRecord, \
MxValue, NaptrRecord, NaptrValue, NsRecord, PtrRecord, Record, \
SshfpRecord, SshfpValue, SpfRecord, SrvRecord, SrvValue, TxtRecord, \
Update, ValidationError, _Dynamic, _DynamicPool, _DynamicRule
from octodns.zone import Zone
from helpers import DynamicProvider, GeoProvider, SimpleProvider
@@ -55,6 +55,19 @@ class TestRecord(TestCase):
})
self.assertEquals(upper_record.value, lower_record.value)
def test_dname_lowering_value(self):
upper_record = DnameRecord(self.zone, 'DnameUppwerValue', {
'ttl': 30,
'type': 'DNAME',
'value': 'GITHUB.COM',
})
lower_record = DnameRecord(self.zone, 'DnameLowerValue', {
'ttl': 30,
'type': 'DNAME',
'value': 'github.com',
})
self.assertEquals(upper_record.value, lower_record.value)
def test_ptr_lowering_value(self):
upper_record = PtrRecord(self.zone, 'PtrUppwerValue', {
'ttl': 30,
@@ -362,6 +375,10 @@ class TestRecord(TestCase):
self.assertSingleValue(CnameRecord, 'target.foo.com.',
'other.foo.com.')
def test_dname(self):
self.assertSingleValue(DnameRecord, 'target.foo.com.',
'other.foo.com.')
def test_mx(self):
a_values = [{
'preference': 10,
@@ -1825,6 +1842,31 @@ class TestRecordValidation(TestCase):
self.assertEquals(['CNAME value "foo.bar.com" missing trailing .'],
ctx.exception.reasons)
def test_DNAME(self):
# A valid DNAME record.
Record.new(self.zone, 'sub', {
'type': 'DNAME',
'ttl': 600,
'value': 'foo.bar.com.',
})
# A DNAME record can be present at the zone APEX.
Record.new(self.zone, '', {
'type': 'DNAME',
'ttl': 600,
'value': 'foo.bar.com.',
})
# missing trailing .
with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, 'www', {
'type': 'DNAME',
'ttl': 600,
'value': 'foo.bar.com',
})
self.assertEquals(['DNAME value "foo.bar.com" missing trailing .'],
ctx.exception.reasons)
def test_MX(self):
# doesn't blow up
Record.new(self.zone, '', {