Browse Source

add Zone File source, reads Bind compatible zone files

pull/276/head
Adam Smith 7 years ago
parent
commit
fd3de1e08b
5 changed files with 114 additions and 2 deletions
  1. +1
    -0
      README.md
  2. +72
    -0
      octodns/source/axfr.py
  3. +33
    -2
      tests/test_octodns_source_axfr.py
  4. +8
    -0
      tests/zones/invalid.zone.
  5. +0
    -0
      tests/zones/unit.tests.

+ 1
- 0
README.md View File

@ -163,6 +163,7 @@ The above command pulled the existing data out of Route53 and placed the results
| [Rackspace](/octodns/provider/rackspace.py) | | A, AAAA, ALIAS, CNAME, MX, NS, PTR, SPF, TXT | No | |
| [Route53](/octodns/provider/route53.py) | boto3 | A, AAAA, CAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, TXT | Yes | |
| [AxfrSource](/octodns/source/axfr.py) | | A, AAAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | read-only |
| [ZoneFileSource](/octodns/source/axfr.py) | | A, AAAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | read-only |
| [TinyDnsFileSource](/octodns/source/tinydns.py) | | A, CNAME, MX, NS, PTR | No | read-only |
| [YamlProvider](/octodns/provider/yaml.py) | | All | Yes | config |


+ 72
- 0
octodns/source/axfr.py View File

@ -13,6 +13,8 @@ import dns.rdatatype
from dns.exception import DNSException
from collections import defaultdict
from os import listdir
from os.path import join
import logging
from ..record import Record
@ -160,3 +162,73 @@ class AxfrSource(AxfrBaseSource):
})
return records
class ZoneFileSourceException(Exception):
pass
class ZoneFileSourceNotFound(ZoneFileSourceException):
def __init__(self):
super(ZoneFileSourceNotFound, self).__init__(
'Zone file not found')
class ZoneFileSourceLoadFailure(ZoneFileSourceException):
def __init__(self, error):
super(ZoneFileSourceLoadFailure, self).__init__(
error.message)
class ZoneFileSource(AxfrBaseSource):
'''
Bind compatible zone file source
zonefile:
class: octodns.source.axfr.ZoneFileSource
# The directory holding the zone files
# Filenames should match zone name (eg. example.com.)
directory: ./zonefiles
'''
def __init__(self, id, directory):
self.log = logging.getLogger('ZoneFileSource[{}]'.format(id))
self.log.debug('__init__: id=%s, directory=%s', id, directory)
super(ZoneFileSource, self).__init__(id)
self.directory = directory
self._zone_records = {}
def _load_zone_file(self, zone_name):
zonefiles = listdir(self.directory)
if zone_name in zonefiles:
try:
z = dns.zone.from_file(join(self.directory, zone_name),
zone_name, relativize=False)
except DNSException as error:
raise ZoneFileSourceLoadFailure(error)
else:
raise ZoneFileSourceNotFound()
return z
def zone_records(self, zone):
if zone.name not in self._zone_records:
try:
z = self._load_zone_file(zone.name)
records = []
for (name, ttl, rdata) in z.iterate_rdatas():
rdtype = dns.rdatatype.to_text(rdata.rdtype)
records.append({
"name": name.to_text(),
"ttl": ttl,
"type": rdtype,
"value": rdata.to_text()
})
self._zone_records[zone.name] = records
except ZoneFileSourceNotFound:
return []
return self._zone_records[zone.name]

+ 33
- 2
tests/test_octodns_source_axfr.py View File

@ -11,14 +11,15 @@ from dns.exception import DNSException
from mock import patch
from unittest import TestCase
from octodns.source.axfr import AxfrSource, AxfrSourceZoneTransferFailed
from octodns.source.axfr import AxfrSource, AxfrSourceZoneTransferFailed, \
ZoneFileSource, ZoneFileSourceLoadFailure
from octodns.zone import Zone
class TestAxfrSource(TestCase):
source = AxfrSource('test', 'localhost')
forward_zonefile = dns.zone.from_file('./tests/zones/unit.tests.db',
forward_zonefile = dns.zone.from_file('./tests/zones/unit.tests.',
'unit.tests', relativize=False)
@patch('dns.zone.from_xfr')
@ -38,3 +39,33 @@ class TestAxfrSource(TestCase):
self.source.populate(zone)
self.assertEquals('Unable to Perform Zone Transfer',
ctx.exception.message)
class TestZoneFileSource(TestCase):
source = ZoneFileSource('test', './tests/zones')
def test_populate(self):
# Valid zone file in directory
valid = Zone('unit.tests.', [])
self.source.populate(valid)
self.assertEquals(11, len(valid.records))
# 2nd populate does not read file again
again = Zone('unit.tests.', [])
self.source.populate(again)
self.assertEquals(11, len(again.records))
# bust the cache
del self.source._zone_records[valid.name]
# No zone file in directory
missing = Zone('missing.zone.', [])
self.source.populate(missing)
self.assertEquals(0, len(missing.records))
# Zone file is not valid
with self.assertRaises(ZoneFileSourceLoadFailure) as ctx:
zone = Zone('invalid.zone.', [])
self.source.populate(zone)
self.assertEquals('The DNS zone has no NS RRset at its origin.',
ctx.exception.message)

+ 8
- 0
tests/zones/invalid.zone. View File

@ -0,0 +1,8 @@
$ORIGIN invalid.zone.
@ IN SOA ns1.invalid.zone. root.invalid.zone. (
2018071501 ; Serial
3600 ; Refresh (1 hour)
600 ; Retry (10 minutes)
604800 ; Expire (1 week)
3600 ; NXDOMAIN ttl (1 hour)
)

tests/zones/unit.tests.db → tests/zones/unit.tests. View File


Loading…
Cancel
Save