Browse Source

Merge branch 'master' into add-selectel

pull/383/head
Ross McFarland 6 years ago
committed by GitHub
parent
commit
de2421aed4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1226 additions and 26 deletions
  1. +1
    -1
      CHANGELOG.md
  2. +525
    -0
      octodns/provider/fastdns.py
  3. +7
    -7
      octodns/provider/route53.py
  4. +1
    -0
      requirements.txt
  5. +35
    -0
      tests/fixtures/fastdns-invalid-content.json
  6. +166
    -0
      tests/fixtures/fastdns-records-prev-other.json
  7. +166
    -0
      tests/fixtures/fastdns-records-prev.json
  8. +157
    -0
      tests/fixtures/fastdns-records.json
  9. +3
    -3
      tests/test_octodns_provider_cloudflare.py
  10. +2
    -2
      tests/test_octodns_provider_digitalocean.py
  11. +2
    -2
      tests/test_octodns_provider_dnsimple.py
  12. +2
    -2
      tests/test_octodns_provider_dnsmadeeasy.py
  13. +150
    -0
      tests/test_octodns_provider_fastdns.py
  14. +2
    -2
      tests/test_octodns_provider_ns1.py
  15. +4
    -4
      tests/test_octodns_provider_powerdns.py
  16. +3
    -3
      tests/test_octodns_record.py

+ 1
- 1
CHANGELOG.md View File

@ -112,7 +112,7 @@ Adds an OVH provider.
## v0.8.6 - 2017-09-06 - CAA record type,
Misc fixes and improvments.
Misc fixes and improvements.
* Azure TXT record fix
* PowerDNS api support for https


+ 525
- 0
octodns/provider/fastdns.py View File

@ -0,0 +1,525 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from requests import Session
from akamai.edgegrid import EdgeGridAuth
from urlparse import urljoin
from collections import defaultdict
from logging import getLogger
from ..record import Record
from .base import BaseProvider
class AkamaiClientNotFound(Exception):
def __init__(self, resp):
message = "404: Resource not found"
super(AkamaiClientNotFound, self).__init__(message)
class AkamaiClient(object):
'''
Client for making calls to Akamai Fast DNS API using Python Requests
Fast DNS Zone Management API V2, found here:
developer.akamai.com/api/web_performance/fast_dns_zone_management/v2.html
Info on Python Requests library:
https://2.python-requests.org/en/master/
'''
def __init__(self, client_secret, host, access_token, client_token):
self.base = "https://" + host + "/config-dns/v2/"
sess = Session()
sess.auth = EdgeGridAuth(
client_token=client_token,
client_secret=client_secret,
access_token=access_token
)
self._sess = sess
def _request(self, method, path, params=None, data=None, v1=False):
url = urljoin(self.base, path)
resp = self._sess.request(method, url, params=params, json=data)
if resp.status_code == 404:
raise AkamaiClientNotFound(resp)
resp.raise_for_status()
return resp
def record_create(self, zone, name, record_type, content):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
result = self._request('POST', path, data=content)
return result
def record_delete(self, zone, name, record_type):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
result = self._request('DELETE', path)
return result
def record_replace(self, zone, name, record_type, content):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
result = self._request('PUT', path, data=content)
return result
def zone_get(self, zone):
path = 'zones/{}'.format(zone)
result = self._request('GET', path)
return result
def zone_create(self, contractId, params, gid=None):
path = 'zones?contractId={}'.format(contractId)
if gid is not None:
path += '&gid={}'.format(gid)
result = self._request('POST', path, data=params)
return result
def zone_recordset_get(self, zone, page=None, pageSize=None, search=None,
showAll="true", sortBy="name", types=None):
params = {
'page': page,
'pageSize': pageSize,
'search': search,
'showAll': showAll,
'sortBy': sortBy,
'types': types
}
path = 'zones/{}/recordsets'.format(zone)
result = self._request('GET', path, params=params)
return result
class AkamaiProvider(BaseProvider):
'''
Akamai Fast DNS Provider
fastdns.py:
Example config file with variables:
"
---
providers:
config:
class: octodns.provider.yaml.YamlProvider
directory: ./config (example path to directory of zone files)
fastdns:
class: octodns.provider.fastdns.AkamaiProvider
client_secret: env/AKAMAI_CLIENT_SECRET
host: env/AKAMAI_HOST
access_token: env/AKAMAI_ACCESS_TOKEN
client_token: env/AKAMAI_CLIENT_TOKEN
contract_id: env/AKAMAI_CONTRACT_ID (optional)
zones:
example.com.:
sources:
- config
targets:
- fastdns
"
The first four variables above can be hidden in environment variables
and octoDNS will automatically search for them in the shell. It is
possible to also hard-code into the config file: eg, contract_id.
The first four values can be found by generating credentials:
https://control.akamai.com/
Configure > Organization > Manage APIs > New API Client for me
Select appropriate group, and fill relevant fields.
For API Service Name, select DNS-Zone Record Management
and then set appropriate Access level (Read-Write to make changes).
Then select the "New Credential" button to generate values for above
The contract_id paramater is optional, and only required for creating
a new zone. If the zone being managed already exists in Akamai for the
user in question, then this paramater is not needed.
'''
SUPPORTS_GEO = False
SUPPORTS_DYNAMIC = False
SUPPORTS = set(('A', 'AAAA', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SPF',
'SRV', 'SSHFP', 'TXT'))
def __init__(self, id, client_secret, host, access_token, client_token,
contract_id=None, gid=None, *args, **kwargs):
self.log = getLogger('AkamaiProvider[{}]'.format(id))
self.log.debug('__init__: id=%s, ')
super(AkamaiProvider, self).__init__(id, *args, **kwargs)
self._dns_client = AkamaiClient(client_secret, host, access_token,
client_token)
self._zone_records = {}
self._contractId = contract_id
self._gid = gid
def zone_records(self, zone):
""" returns records for a zone, looks for it if not present, or
returns empty [] if can't find a match
"""
if zone.name not in self._zone_records:
try:
name = zone.name[:-1]
response = self._dns_client.zone_recordset_get(name)
self._zone_records[zone.name] = response.json()["recordsets"]
except (AkamaiClientNotFound, KeyError):
return []
return self._zone_records[zone.name]
def populate(self, zone, target=False, lenient=False):
self.log.debug('populate: name=%s', zone.name)
values = defaultdict(lambda: defaultdict(list))
for record in self.zone_records(zone):
_type = record.get('type')
# Akamai sends down prefix.zonename., while octodns expects prefix
_name = record.get('name').split("." + zone.name[:-1], 1)[0]
if _name == zone.name[:-1]:
_name = '' # root / @
if _type not in self.SUPPORTS:
continue
values[_name][_type].append(record)
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
record = Record.new(zone, name, data_for(_type, records[0]),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
exists = zone.name in self._zone_records
found = len(zone.records) - before
self.log.info('populate: found %s records, exists=%s', found, exists)
return exists
def _apply(self, plan):
desired = plan.desired
changes = plan.changes
self.log.debug('apply: zone=%s, chnges=%d', desired.name, len(changes))
zone_name = desired.name[:-1]
try:
self._dns_client.zone_get(zone_name)
except AkamaiClientNotFound:
self.log.info("zone not found, creating zone")
params = self._build_zone_config(zone_name)
self._dns_client.zone_create(self._contractId, params, self._gid)
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)
def _apply_Create(self, change):
new = change.new
record_type = new._type
params_for = getattr(self, '_params_for_{}'.format(record_type))
values = self._get_values(new.data)
rdata = params_for(values)
zone = new.zone.name[:-1]
name = self._set_full_name(new.name, zone)
content = {
"name": name,
"type": record_type,
"ttl": new.ttl,
"rdata": rdata
}
self._dns_client.record_create(zone, name, record_type, content)
return
def _apply_Delete(self, change):
zone = change.existing.zone.name[:-1]
name = self._set_full_name(change.existing.name, zone)
record_type = change.existing._type
self._dns_client.record_delete(zone, name, record_type)
return
def _apply_Update(self, change):
new = change.new
record_type = new._type
params_for = getattr(self, '_params_for_{}'.format(record_type))
values = self._get_values(new.data)
rdata = params_for(values)
zone = new.zone.name[:-1]
name = self._set_full_name(new.name, zone)
content = {
"name": name,
"type": record_type,
"ttl": new.ttl,
"rdata": rdata
}
self._dns_client.record_replace(zone, name, record_type, content)
return
def _data_for_multiple(self, _type, records):
return {
'ttl': records['ttl'],
'type': _type,
'values': [r for r in records['rdata']]
}
_data_for_A = _data_for_multiple
_data_for_AAAA = _data_for_multiple
_data_for_NS = _data_for_multiple
_data_for_SPF = _data_for_multiple
def _data_for_CNAME(self, _type, records):
value = records['rdata'][0]
if (value[-1] != '.'):
value = '{}.'.format(value)
return {
'ttl': records['ttl'],
'type': _type,
'value': value
}
def _data_for_MX(self, _type, records):
values = []
for r in records['rdata']:
preference, exchange = r.split(" ", 1)
values.append({
'preference': preference,
'exchange': exchange
})
return {
'ttl': records['ttl'],
'type': _type,
'values': values
}
def _data_for_NAPTR(self, _type, records):
values = []
for r in records['rdata']:
order, preference, flags, service, regexp, repl = r.split(' ', 5)
values.append({
'flags': flags[1:-1],
'order': order,
'preference': preference,
'regexp': regexp[1:-1],
'replacement': repl,
'service': service[1:-1]
})
return {
'type': _type,
'ttl': records['ttl'],
'values': values
}
def _data_for_PTR(self, _type, records):
return {
'ttl': records['ttl'],
'type': _type,
'value': records['rdata'][0]
}
def _data_for_SRV(self, _type, records):
values = []
for r in records['rdata']:
priority, weight, port, target = r.split(' ', 3)
values.append({
'port': port,
'priority': priority,
'target': target,
'weight': weight
})
return {
'type': _type,
'ttl': records['ttl'],
'values': values
}
def _data_for_SSHFP(self, _type, records):
values = []
for r in records['rdata']:
algorithm, fp_type, fingerprint = r.split(' ', 2)
values.append({
'algorithm': algorithm,
'fingerprint': fingerprint.lower(),
'fingerprint_type': fp_type
})
return {
'type': _type,
'ttl': records['ttl'],
'values': values
}
def _data_for_TXT(self, _type, records):
values = []
for r in records['rdata']:
r = r[1:-1]
values.append(r.replace(';', '\\;'))
return {
'ttl': records['ttl'],
'type': _type,
'values': values
}
def _params_for_multiple(self, values):
return [r for r in values]
def _params_for_single(self, values):
return values
_params_for_A = _params_for_multiple
_params_for_AAAA = _params_for_multiple
_params_for_NS = _params_for_multiple
_params_for_CNAME = _params_for_single
_params_for_PTR = _params_for_single
def _params_for_MX(self, values):
rdata = []
for r in values:
preference = r['preference']
exchange = r['exchange']
record = '{} {}'.format(preference, exchange)
rdata.append(record)
return rdata
def _params_for_NAPTR(self, values):
rdata = []
for r in values:
ordr = r['order']
prf = r['preference']
flg = "\"" + r['flags'] + "\""
srvc = "\"" + r['service'] + "\""
rgx = "\"" + r['regexp'] + "\""
rpl = r['replacement']
record = '{} {} {} {} {} {}'.format(ordr, prf, flg, srvc, rgx, rpl)
rdata.append(record)
return rdata
def _params_for_SPF(self, values):
rdata = []
for r in values:
txt = "\"" + r.replace('\\;', ';') + "\""
rdata.append(txt)
return rdata
def _params_for_SRV(self, values):
rdata = []
for r in values:
priority = r['priority']
weight = r['weight']
port = r['port']
target = r['target']
record = '{} {} {} {}'.format(priority, weight, port, target)
rdata.append(record)
return rdata
def _params_for_SSHFP(self, values):
rdata = []
for r in values:
algorithm = r['algorithm']
fp_type = r['fingerprint_type']
fp = r['fingerprint']
record = '{} {} {}'.format(algorithm, fp_type, fp)
rdata.append(record)
return rdata
def _params_for_TXT(self, values):
rdata = []
for r in values:
txt = "\"" + r.replace('\\;', ';') + "\""
rdata.append(txt)
return rdata
def _build_zone_config(self, zone, _type="primary", comment=None,
masters=[]):
if self._contractId is None:
raise NameError("contractId not specified to create zone")
return {
"zone": zone,
"type": _type,
"comment": comment,
"masters": masters
}
def _get_values(self, data):
try:
vals = data['values']
except KeyError:
vals = [data['value']]
return vals
def _set_full_name(self, name, zone):
name = name + '.' + zone
# octodns's name for root is ''
if (name[0] == '.'):
name = name[1:]
return name

+ 7
- 7
octodns/provider/route53.py View File

@ -528,13 +528,13 @@ def _mod_keyer(mod):
# before all changes, followed by a "CREATE", internally in the AWS API.
# Because of this, we order changes as follows:
# - Delete any records that we wish to delete that are GEOS
# (because they are never targetted by anything)
# (because they are never targeted by anything)
# - Delete any records that we wish to delete that are SECONDARY
# (because they are no longer targetted by GEOS)
# (because they are no longer targeted by GEOS)
# - Delete any records that we wish to delete that are PRIMARY
# (because they are no longer targetted by SECONDARY)
# (because they are no longer targeted by SECONDARY)
# - Delete any records that we wish to delete that are VALUES
# (because they are no longer targetted by PRIMARY)
# (because they are no longer targeted by PRIMARY)
# - CREATE/UPSERT any records that are VALUES
# (because they don't depend on other records)
# - CREATE/UPSERT any records that are PRIMARY
@ -1028,7 +1028,7 @@ class Route53Provider(BaseProvider):
.get('healthcheck', {}) \
.get('measure_latency', True)
def _health_check_equivilent(self, host, path, protocol, port,
def _health_check_equivalent(self, host, path, protocol, port,
measure_latency, health_check, value=None):
config = health_check['HealthCheckConfig']
@ -1080,7 +1080,7 @@ class Route53Provider(BaseProvider):
if not health_check['CallerReference'].startswith(expected_ref):
# not match, ignore
continue
if self._health_check_equivilent(healthcheck_host,
if self._health_check_equivalent(healthcheck_host,
healthcheck_path,
healthcheck_protocol,
healthcheck_port,
@ -1237,7 +1237,7 @@ class Route53Provider(BaseProvider):
health_check = self.health_checks[health_check_id]
caller_ref = health_check['CallerReference']
if caller_ref.startswith(self.HEALTH_CHECK_VERSION):
if self._health_check_equivilent(healthcheck_host,
if self._health_check_equivalent(healthcheck_host,
healthcheck_path,
healthcheck_protocol,
healthcheck_port,


+ 1
- 0
requirements.txt View File

@ -6,6 +6,7 @@ botocore==1.10.5
dnspython==1.15.0
docutils==0.14
dyn==1.8.1
edgegrid-python==1.1.1
futures==3.2.0
google-cloud-core==0.28.1
google-cloud-dns==0.29.0


+ 35
- 0
tests/fixtures/fastdns-invalid-content.json View File

@ -0,0 +1,35 @@
{
"recordsets": [
{
"rdata": [
"",
"12 20 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.unit.tests",
"ttl": 600
},
{
"rdata": [
"",
"1 1"
],
"type": "SSHFP",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"",
"100 \"U\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" ."
],
"type": "NAPTR",
"name": "naptr.unit.tests",
"ttl": 600
}
],
"metadata": {
"totalElements": 3,
"showAll": true
}
}

+ 166
- 0
tests/fixtures/fastdns-records-prev-other.json View File

@ -0,0 +1,166 @@
{
"recordsets": [
{
"rdata": [
"10 20 30 foo-1.other.tests.",
"12 20 30 foo-2.other.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.other.tests",
"ttl": 600
},
{
"rdata": [
"10 20 30 foo-1.other.tests.",
"12 20 30 foo-2.other.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.other.tests",
"ttl": 600
},
{
"rdata": [
"2601:644:500:e210:62f8:1dff:feb8:9471"
],
"type": "AAAA",
"name": "aaaa.old.other.tests",
"ttl": 600
},
{
"rdata": [
"ns1.akam.net.",
"ns2.akam.net.",
"ns3.akam.net.",
"ns4.akam.net."
],
"type": "NS",
"name": "old.other.tests",
"ttl": 3600
},
{
"rdata": [
"1.2.3.4",
"1.2.3.5"
],
"type": "A",
"name": "old.other.tests",
"ttl": 300
},
{
"rdata": [
"ns1.akam.net hostmaster.akamai.com 1489074932 86400 7200 604800 300"
],
"type": "SOA",
"name": "other.tests",
"ttl": 3600
},
{
"rdata": [
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
],
"type": "SSHFP",
"name": "old.other.tests",
"ttl": 3600
},
{
"rdata": [
"other.tests."
],
"type": "CNAME",
"name": "old.cname.other.tests",
"ttl": 300
},
{
"rdata": [
"other.tests."
],
"type": "CNAME",
"name": "excluded.old.other.tests",
"ttl": 3600
},
{
"rdata": [
"other.tests."
],
"type": "CNAME",
"name": "included.old.other.tests",
"ttl": 3600
},
{
"rdata": [
"10 smtp-4.other.tests.",
"20 smtp-2.other.tests.",
"30 smtp-3.other.tests.",
"40 smtp-1.other.tests."
],
"type": "MX",
"name": "mx.old.other.tests",
"ttl": 300
},
{
"rdata": [
"10 100 \"S\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" .",
"100 100 \"U\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" ."
],
"type": "NAPTR",
"name": "naptr.old.other.tests",
"ttl": 600
},
{
"rdata": [
"foo.bar.com."
],
"type": "PTR",
"name": "ptr.old.other.tests",
"ttl": 300
},
{
"rdata": [
"\"v=spf1 ip4:192.168.0.1/16-all\""
],
"type": "SPF",
"name": "spf.old.other.tests",
"ttl": 600
},
{
"rdata": [
"ns1.other.tests.",
"ns2.other.tests."
],
"type": "NS",
"name": "under.old.other.tests",
"ttl": 3600
},
{
"rdata": [
"\"Bah bah black sheep\"",
"\"have you any wool.\"",
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
],
"type": "TXT",
"name": "txt.old.other.tests",
"ttl": 600
},
{
"rdata": [
"2.2.3.7"
],
"type": "A",
"name": "www.other.tests",
"ttl": 300
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.sub.old.other.tests",
"ttl": 300
}
],
"metadata": {
"totalElements": 16,
"showAll": true
}
}

+ 166
- 0
tests/fixtures/fastdns-records-prev.json View File

@ -0,0 +1,166 @@
{
"recordsets": [
{
"rdata": [
"10 20 30 foo-1.unit.tests.",
"12 20 30 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"10 20 30 foo-1.unit.tests.",
"12 20 30 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"2601:644:500:e210:62f8:1dff:feb8:9471"
],
"type": "AAAA",
"name": "aaaa.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.akam.net.",
"ns2.akam.net.",
"ns3.akam.net.",
"ns4.akam.net."
],
"type": "NS",
"name": "old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"1.2.3.4",
"1.2.3.5"
],
"type": "A",
"name": "old.unit.tests",
"ttl": 300
},
{
"rdata": [
"ns1.akam.net hostmaster.akamai.com 1489074932 86400 7200 604800 300"
],
"type": "SOA",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
],
"type": "SSHFP",
"name": "old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests"
],
"type": "CNAME",
"name": "old.cname.unit.tests",
"ttl": 300
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "excluded.old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "included.old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"10 smtp-4.unit.tests.",
"20 smtp-2.unit.tests.",
"30 smtp-3.unit.tests.",
"40 smtp-1.unit.tests."
],
"type": "MX",
"name": "mx.old.unit.tests",
"ttl": 300
},
{
"rdata": [
"10 100 \"S\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" .",
"100 100 \"U\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" ."
],
"type": "NAPTR",
"name": "naptr.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"foo.bar.com."
],
"type": "PTR",
"name": "ptr.old.unit.tests",
"ttl": 300
},
{
"rdata": [
"\"v=spf1 ip4:192.168.0.1/16-all\""
],
"type": "SPF",
"name": "spf.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.unit.tests.",
"ns2.unit.tests."
],
"type": "NS",
"name": "under.old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"\"Bah bah black sheep\"",
"\"have you any wool.\"",
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
],
"type": "TXT",
"name": "txt.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"2.2.3.7"
],
"type": "A",
"name": "www.unit.tests",
"ttl": 300
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.sub.old.unit.tests",
"ttl": 300
}
],
"metadata": {
"totalElements": 16,
"showAll": true
}
}

+ 157
- 0
tests/fixtures/fastdns-records.json View File

@ -0,0 +1,157 @@
{
"recordsets": [
{
"rdata": [
"10 20 30 foo-1.unit.tests.",
"12 20 30 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.unit.tests",
"ttl": 600
},
{
"rdata": [
"2601:644:500:e210:62f8:1dff:feb8:947a"
],
"type": "AAAA",
"name": "aaaa.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.akam.net.",
"ns2.akam.net.",
"ns3.akam.net.",
"ns4.akam.net."
],
"type": "NS",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"1.2.3.4",
"1.2.3.5"
],
"type": "A",
"name": "unit.tests",
"ttl": 300
},
{
"rdata": [
"ns1.akam.net hostmaster.akamai.com 1489074932 86400 7200 604800 300"
],
"type": "SOA",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
],
"type": "SSHFP",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "cname.unit.tests",
"ttl": 300
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "excluded.unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "included.unit.tests",
"ttl": 3600
},
{
"rdata": [
"10 smtp-4.unit.tests.",
"20 smtp-2.unit.tests.",
"30 smtp-3.unit.tests.",
"40 smtp-1.unit.tests."
],
"type": "MX",
"name": "mx.unit.tests",
"ttl": 300
},
{
"rdata": [
"10 100 \"S\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" .",
"100 100 \"U\" \"SIP+D2U\" \"!^.*$!sip:info@bar.example.com!\" ."
],
"type": "NAPTR",
"name": "naptr.unit.tests",
"ttl": 600
},
{
"rdata": [
"foo.bar.com."
],
"type": "PTR",
"name": "ptr.unit.tests",
"ttl": 300
},
{
"rdata": [
"\"v=spf1 ip4:192.168.0.1/16-all\""
],
"type": "SPF",
"name": "spf.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.unit.tests.",
"ns2.unit.tests."
],
"type": "NS",
"name": "under.unit.tests",
"ttl": 3600
},
{
"rdata": [
"\"Bah bah black sheep\"",
"\"have you any wool.\"",
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
],
"type": "TXT",
"name": "txt.unit.tests",
"ttl": 600
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.unit.tests",
"ttl": 300
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.sub.unit.tests",
"ttl": 300
}
],
"metadata": {
"totalElements": 16,
"showAll": true
}
}

+ 3
- 3
tests/test_octodns_provider_cloudflare.py View File

@ -102,7 +102,7 @@ class TestCloudflareProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=200, json=self.empty)
@ -110,7 +110,7 @@ class TestCloudflareProvider(TestCase):
provider.populate(zone)
self.assertEquals(set(), zone.records)
# re-populating the same non-existant zone uses cache and makes no
# re-populating the same non-existent zone uses cache and makes no
# calls
again = Zone('unit.tests.', [])
provider.populate(again)
@ -173,7 +173,7 @@ class TestCloudflareProvider(TestCase):
}, # zone create
] + [None] * 20 # individual record creates
# non-existant zone, create everything
# non-existent zone, create everything
plan = provider.plan(self.expected)
self.assertEquals(12, len(plan.changes))
self.assertEquals(12, provider.apply(plan))


+ 2
- 2
tests/test_octodns_provider_digitalocean.py View File

@ -61,7 +61,7 @@ class TestDigitalOceanProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='{"id":"not_found","message":"The resource you '
@ -153,7 +153,7 @@ class TestDigitalOceanProvider(TestCase):
}
}
# non-existant domain, create everything
# non-existent domain, create everything
resp.json.side_effect = [
DigitalOceanClientNotFound, # no zone in populate
DigitalOceanClientNotFound, # no domain during apply


+ 2
- 2
tests/test_octodns_provider_dnsimple.py View File

@ -58,7 +58,7 @@ class TestDnsimpleProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='{"message": "Domain `foo.bar` not found"}')
@ -122,7 +122,7 @@ class TestDnsimpleProvider(TestCase):
resp.json = Mock()
provider._client._request = Mock(return_value=resp)
# non-existant domain, create everything
# non-existent domain, create everything
resp.json.side_effect = [
DnsimpleClientNotFound, # no zone in populate
DnsimpleClientNotFound, # no domain during apply


+ 2
- 2
tests/test_octodns_provider_dnsmadeeasy.py View File

@ -87,7 +87,7 @@ class TestDnsMadeEasyProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='<html><head></head><body></body></html>')
@ -130,7 +130,7 @@ class TestDnsMadeEasyProvider(TestCase):
with open('tests/fixtures/dnsmadeeasy-domains.json') as fh:
domains = json.load(fh)
# non-existant domain, create everything
# non-existent domain, create everything
resp.json.side_effect = [
DnsMadeEasyClientNotFound, # no zone in populate
DnsMadeEasyClientNotFound, # no domain during apply


+ 150
- 0
tests/test_octodns_provider_fastdns.py View File

@ -0,0 +1,150 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
# from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from unittest import TestCase
from octodns.record import Record
from octodns.provider.fastdns import AkamaiProvider
from octodns.provider.yaml import YamlProvider
from octodns.zone import Zone
class TestFastdnsProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
# Our test suite differs a bit, add our NS and remove the simple one
expected.add_record(Record.new(expected, 'under', {
'ttl': 3600,
'type': 'NS',
'values': [
'ns1.unit.tests.',
'ns2.unit.tests.',
]
}))
for record in list(expected.records):
if record.name == 'sub' and record._type == 'NS':
expected._remove_record(record)
break
def test_populate(self):
provider = AkamaiProvider("test", "secret", "akam.com", "atok", "ctok")
# Bad Auth
with requests_mock() as mock:
mock.get(ANY, status_code=401, text='{"message": "Unauthorized"}')
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(401, ctx.exception.response.status_code)
# general error
with requests_mock() as mock:
mock.get(ANY, status_code=502, text='Things caught fire')
with self.assertRaises(HTTPError) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='{"message": "Domain `foo.bar` not found"}')
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(set(), zone.records)
# No diffs == no changes
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records.json') as fh:
mock.get(ANY, text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(16, len(zone.records))
changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', [])
provider.populate(again)
self.assertEquals(16, len(again.records))
# bust the cache
del provider._zone_records[zone.name]
def test_apply(self):
provider = AkamaiProvider("test", "s", "akam.com", "atok", "ctok",
"cid", "gid")
# tests create update delete through previous state config json
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records-prev.json') as fh:
mock.get(ANY, text=fh.read())
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
changes = provider.apply(plan)
self.assertEquals(29, changes)
# Test against a zone that doesn't exist yet
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records-prev-other.json') as fh:
mock.get(ANY, status_code=404)
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
changes = provider.apply(plan)
self.assertEquals(14, changes)
# Test against a zone that doesn't exist yet, but gid not provided
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records-prev-other.json') as fh:
mock.get(ANY, status_code=404)
provider = AkamaiProvider("test", "s", "akam.com", "atok", "ctok",
"cid")
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
changes = provider.apply(plan)
self.assertEquals(14, changes)
# Test against a zone that doesn't exist, but cid not provided
with requests_mock() as mock:
mock.get(ANY, status_code=404)
provider = AkamaiProvider("test", "s", "akam.com", "atok", "ctok")
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
try:
changes = provider.apply(plan)
except NameError as e:
expected = "contractId not specified to create zone"
self.assertEquals(e.message, expected)

+ 2
- 2
tests/test_octodns_provider_ns1.py View File

@ -191,7 +191,7 @@ class TestNs1Provider(TestCase):
self.assertEquals(load_mock.side_effect, ctx.exception)
self.assertEquals(('unit.tests',), load_mock.call_args[0])
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
load_mock.reset_mock()
load_mock.side_effect = \
ResourceException('server error: zone not found')
@ -323,7 +323,7 @@ class TestNs1Provider(TestCase):
provider.apply(plan)
self.assertEquals(create_mock.side_effect, ctx.exception)
# non-existant zone, create
# non-existent zone, create
load_mock.reset_mock()
create_mock.reset_mock()
load_mock.side_effect = \


+ 4
- 4
tests/test_octodns_provider_powerdns.py View File

@ -41,7 +41,7 @@ with open('./tests/fixtures/powerdns-full-data.json') as fh:
class TestPowerDnsProvider(TestCase):
def test_provider(self):
provider = PowerDnsProvider('test', 'non.existant', 'api-key',
provider = PowerDnsProvider('test', 'non.existent', 'api-key',
nameserver_values=['8.8.8.8.',
'9.9.9.9.'])
@ -63,7 +63,7 @@ class TestPowerDnsProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=422,
json={'error': "Could not find domain 'unit.tests.'"})
@ -163,7 +163,7 @@ class TestPowerDnsProvider(TestCase):
provider.apply(plan)
def test_small_change(self):
provider = PowerDnsProvider('test', 'non.existant', 'api-key')
provider = PowerDnsProvider('test', 'non.existent', 'api-key')
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
@ -203,7 +203,7 @@ class TestPowerDnsProvider(TestCase):
def test_existing_nameservers(self):
ns_values = ['8.8.8.8.', '9.9.9.9.']
provider = PowerDnsProvider('test', 'non.existant', 'api-key',
provider = PowerDnsProvider('test', 'non.existent', 'api-key',
nameserver_values=ns_values)
expected = Zone('unit.tests.', [])


+ 3
- 3
tests/test_octodns_record.py View File

@ -2845,7 +2845,7 @@ class TestDynamicRecords(TestCase):
self.assertEquals(['rule 1 invalid pool "[]"'],
ctx.exception.reasons)
# rule references non-existant pool
# rule references non-existent pool
a_data = {
'dynamic': {
'pools': {
@ -2864,7 +2864,7 @@ class TestDynamicRecords(TestCase):
},
'rules': [{
'geos': ['NA-US-CA'],
'pool': 'non-existant',
'pool': 'non-existent',
}, {
'pool': 'one',
}],
@ -2878,7 +2878,7 @@ class TestDynamicRecords(TestCase):
}
with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, 'bad', a_data)
self.assertEquals(["rule 1 undefined pool \"non-existant\""],
self.assertEquals(["rule 1 undefined pool \"non-existent\""],
ctx.exception.reasons)
# rule with invalid geos


Loading…
Cancel
Save