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

Merge branch 'master' into extract-easydns

This commit is contained in:
Ross McFarland
2022-01-09 08:24:27 -08:00
committed by GitHub
10 changed files with 31 additions and 1204 deletions

View File

@@ -6,6 +6,7 @@
https://github.com/octodns/octodns/issues/622 &
https://github.com/octodns/octodns/pull/822 for more information. Providers
that have been extracted in this release include:
* [AkamaiProvider](https://github.com/octodns/octodns-edgedns/)
* [CloudflareProvider](https://github.com/octodns/octodns-cloudflare/)
* [ConstellixProvider](https://github.com/octodns/octodns-constellix/)
* [DigitalOceanProvider](https://github.com/octodns/octodns-digitalocean/)

View File

@@ -193,7 +193,7 @@ The table below lists the providers octoDNS supports. We're currently in the pro
| Provider | Module | Requirements | Record Support | Dynamic | Notes |
|--|--|--|--|--|--|
| [AzureProvider](/octodns/provider/azuredns.py) | | azure-identity, azure-mgmt-dns, azure-mgmt-trafficmanager | A, AAAA, CAA, CNAME, MX, NS, PTR, SRV, TXT | Alpha (A, AAAA, CNAME) | |
| [Akamai](/octodns/provider/edgedns.py) | | edgegrid-python | A, AAAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, SSHFP, TXT | No | |
| [AkamaiProvider](https://github.com/octodns/octodns-edgedns/) | [octodns_edgedns](https://github.com/octodns/octodns-edgedns/) | | | | |
| [CloudflareProvider](https://github.com/octodns/octodns-cloudflare/) | [octodns_cloudflare](https://github.com/octodns/octodns-cloudflare/) | | | | |
| [ConstellixProvider](https://github.com/octodns/octodns-constellix/) | [octodns_constellix](https://github.com/octodns/octodns-constellix/) | | | | |
| [DigitalOceanProvider](https://github.com/octodns/octodns-digitalocean/) | [octodns_digitalocean](https://github.com/octodns/octodns-digitalocean/) | | | | |

View File

@@ -5,515 +5,19 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
from requests import Session
from akamai.edgegrid import EdgeGridAuth
from collections import defaultdict
from urllib.parse import urljoin
from logging import getLogger
from ..record import Record
from . import ProviderException
from .base import BaseProvider
class AkamaiClientNotFound(ProviderException):
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
Edge DNS Zone Management API V2, found here:
https://developer.akamai.com/api/cloud_security/edge_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 = f'zones/{zone}/names/{name}/types/{record_type}'
result = self._request('POST', path, data=content)
return result
def record_delete(self, zone, name, record_type):
path = f'zones/{zone}/names/{name}/types/{record_type}'
result = self._request('DELETE', path)
return result
def record_replace(self, zone, name, record_type, content):
path = f'zones/{zone}/names/{name}/types/{record_type}'
result = self._request('PUT', path, data=content)
return result
def zone_get(self, zone):
path = f'zones/{zone}'
result = self._request('GET', path)
return result
def zone_create(self, contractId, params, gid=None):
path = f'zones?contractId={contractId}'
if gid is not None:
path += f'&gid={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 = f'zones/{zone}/recordsets'
result = self._request('GET', path, params=params)
return result
class AkamaiProvider(BaseProvider):
'''
Akamai Edge DNS Provider
edgedns.py:
Example config file with variables:
"
---
providers:
config:
class: octodns.provider.yaml.YamlProvider
directory: ./config (example path to directory of zone files)
edgedns:
class: octodns.provider.edgedns.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:
- edgedns
"
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(f'AkamaiProvider[{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, f'_data_for_{_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, f'_apply_{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, f'_params_for_{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, f'_params_for_{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 = f'{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']
rdata.append(f'{preference} {exchange}')
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']
rdata.append(f'{ordr} {prf} {flg} {srvc} {rgx} {rpl}')
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']
rdata.append(f'{priority} {weight} {port} {target}')
return rdata
def _params_for_SSHFP(self, values):
rdata = []
for r in values:
algorithm = r['algorithm']
fp_type = r['fingerprint_type']
fp = r['fingerprint']
rdata.append(f'{algorithm} {fp_type} {fp}')
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
logger = getLogger('Akamai')
try:
logger.warn('octodns_edgedns shimmed. Update your provider class to '
'octodns_edgedns.AkamaiProvider. '
'Shim will be removed in 1.0')
from octodns_edgedns import AkamaiProvider
AkamaiProvider # pragma: no cover
except ModuleNotFoundError:
logger.exception('AkamaiProvider has been moved into a seperate module, '
'octodns_edgedns is now required. Provider class should '
'be updated to octodns_edgedns.AkamaiProvider. See '
'https://github.com/octodns/octodns/README.md#updating-'
'to-use-extracted-providers for more information.')
raise

View File

@@ -5,12 +5,11 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
from .edgedns import AkamaiProvider
from logging import getLogger
# Quell unused warning
AkamaiProvider
log = getLogger('octodns.provider.fastdns.AkamaiProvider')
log.warn('DEPRECATION NOTICE: AkamaiProvider has been moved to '
'octodns.provider.fastdns.AkamaiProvider')
logger = getLogger('Akamai')
logger.warn('AkamaiProvider has been moved into a seperate module, '
'octodns_edgedns is now required. Provider class should '
'be updated to octodns_edgedns.AkamaiProvider. See '
'https://github.com/octodns/octodns/README.md#updating-'
'to-use-extracted-providers for more information.')

View File

@@ -5,7 +5,6 @@ azure-mgmt-dns==8.0.0
azure-mgmt-trafficmanager==0.51.0
dnspython==1.16.0
docutils==0.16
edgegrid-python==1.1.1
fqdn==1.5.0
google-cloud-core==1.4.1
google-cloud-dns==0.32.0

View File

@@ -1,35 +0,0 @@
{
"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
}
}

View File

@@ -1,166 +0,0 @@
{
"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
}
}

View File

@@ -1,166 +0,0 @@
{
"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
}
}

View File

@@ -1,173 +0,0 @@
{
"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": [
"0 0 0 ."
],
"type": "SRV",
"name": "_imap._tcp.unit.tests",
"ttl": 600
},
{
"rdata": [
"0 0 0 ."
],
"type": "SRV",
"name": "_pop3._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": 18,
"showAll": true
}
}

View File

@@ -5,153 +5,17 @@
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.edgedns import AkamaiProvider
from octodns.provider.fastdns import AkamaiProvider as LegacyAkamaiProvider
from octodns.provider.yaml import YamlProvider
from octodns.zone import Zone
# Just for coverage
import octodns.provider.fastdns
# Quell warnings
octodns.provider.fastdns
class TestEdgeDnsProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
class TestAkamaiShim(TestCase):
# 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/edgedns-records.json') as fh:
mock.get(ANY, text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(18, 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(18, 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/edgedns-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(31, changes)
# Test against a zone that doesn't exist yet
with requests_mock() as mock:
with open('tests/fixtures/edgedns-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(16, changes)
# Test against a zone that doesn't exist yet, but gid not provided
with requests_mock() as mock:
with open('tests/fixtures/edgedns-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(16, 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(str(e), expected)
class TestDeprecatedAkamaiProvider(TestCase):
def test_equivilent(self):
self.assertEquals(LegacyAkamaiProvider, AkamaiProvider)
def test_missing(self):
with self.assertRaises(ModuleNotFoundError):
from octodns.provider.edgedns import AkamaiProvider
AkamaiProvider