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

cleaned up code

This commit is contained in:
Basir Jamil
2019-06-21 15:04:27 -04:00
parent 3edf6969f8
commit 8417a4475a

View File

@@ -5,11 +5,9 @@
from __future__ import absolute_import, division, print_function, \ from __future__ import absolute_import, division, print_function, \
unicode_literals unicode_literals
## octodns specfic imports:
import requests import requests
from akamai.edgegrid import EdgeGridAuth from akamai.edgegrid import EdgeGridAuth
from urlparse import urljoin from urlparse import urljoin
import json
from collections import defaultdict from collections import defaultdict
import logging import logging
@@ -32,17 +30,30 @@ class AkamaiClientException(Exception):
500: "500: Internal server error" 500: "500: Internal server error"
} }
def __init__(self, code): def __init__(self, resp):
message = self._errorMessages.get(code) try:
message = self._errorMessages.get(resp.status_code)
super(AkamaiClientException, self).__init__(message) super(AkamaiClientException, self).__init__(message)
except:
resp.raise_for_status()
class AkamaiClient(object): 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): def __init__(self, _client_secret, _host, _access_token, _client_token):
self.base = "https://" + _host + "/config-dns/v2/" self.base = "https://" + _host + "/config-dns/v2/"
self.basev1 = "https://" + _host + "/config-dns/v1/"
sess = requests.Session() sess = requests.Session()
sess.auth = EdgeGridAuth( sess.auth = EdgeGridAuth(
@@ -56,25 +67,14 @@ class AkamaiClient(object):
def _request(self, method, path, params=None, data=None, v1=False): def _request(self, method, path, params=None, data=None, v1=False):
url = urljoin(self.base, path) url = urljoin(self.base, path)
if v1:
url = urljoin(self.basev1, path)
resp = self._sess.request(method, url, params=params, json=data) resp = self._sess.request(method, url, params=params, json=data)
if resp.status_code > 299: if resp.status_code > 299:
raise AkamaiClientException(resp.status_code) raise AkamaiClientException(resp)
resp.raise_for_status()
return resp return resp
def record_get(self, zone, name, record_type):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
result = self._request('GET', path)
return result
def record_create(self, zone, name, record_type, content): def record_create(self, zone, name, record_type, content):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type) path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
result = self._request('POST', path, data=content) result = self._request('POST', path, data=content)
@@ -83,12 +83,8 @@ class AkamaiClient(object):
def record_delete(self, zone, name, record_type): def record_delete(self, zone, name, record_type):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type) path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
print(path)
result = self._request('DELETE', path) result = self._request('DELETE', path)
if result.status_code == 204:
print ("successfully deleted ", path)
return result return result
def record_replace(self, zone, name, record_type, content): def record_replace(self, zone, name, record_type, content):
@@ -115,24 +111,6 @@ class AkamaiClient(object):
return result return result
def zones_get(self, contractIds=None, page=None, pageSize=None, search=None,
showAll="true", sortBy="zone", types=None):
path = 'zones'
params = {
'contractIds': contractIds,
'page': page,
'pageSize': pageSize,
'search': search,
'showAll': showAll,
'sortBy': sortBy,
'types': types
}
result = self._request('GET', path, params=params)
return result
def zone_recordset_get(self, zone, page=None, pageSize=30, search=None, def zone_recordset_get(self, zone, page=None, pageSize=30, search=None,
showAll="true", sortBy="name", types=None): showAll="true", sortBy="name", types=None):
@@ -151,16 +129,6 @@ class AkamaiClient(object):
return result return result
def contracts_get(self, gid=None):
path = 'data/contracts'
if gid is not None:
path += '?gid={}'.format(gid)
result = self._request('GET', path)
return result
def recordsets_get(self, zone_name): def recordsets_get(self, zone_name):
resp = self.zone_recordset_get(zone_name, showAll="true") resp = self.zone_recordset_get(zone_name, showAll="true")
@@ -168,25 +136,56 @@ class AkamaiClient(object):
return recordset return recordset
def master_zone_file_get(self, zone):
path = 'zones/{}/zone-file'.format(zone)
try:
result = self._request('GET', path)
except AkamaiClientException as e:
# not working with API v2, API v1 fallback
path = 'zones/{}'.format(zone)
result = self._request('GET', path, v1=True)
print("Using API v1 fallback")
print("(Probably Ignore)", e.message)
return result
class AkamaiProvider(BaseProvider): 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_GEO = False
SUPPORTS_DYNAMIC = False SUPPORTS_DYNAMIC = False
@@ -208,8 +207,8 @@ class AkamaiProvider(BaseProvider):
self._gid = gid self._gid = gid
def zone_records(self, zone): def zone_records(self, zone):
""" returns records for a zone, finds it if not present, or """ returns records for a zone, looks for it if not present, or
returns empty if can't find a match returns empty [] if can't find a match
""" """
if zone.name not in self._zone_records: if zone.name not in self._zone_records:
try: try:
@@ -228,7 +227,7 @@ class AkamaiProvider(BaseProvider):
for record in self.zone_records(zone): for record in self.zone_records(zone):
_type =record.get('type') _type =record.get('type')
## Akamai sends down prefix.zonename., while OctoDNS only expects prefix ## Akamai sends down prefix.zonename., while octodns expects prefix
_name = record.get('name').split("." + zone.name[:-1], 1)[0] _name = record.get('name').split("." + zone.name[:-1], 1)[0]
if _name == zone.name[:-1] : if _name == zone.name[:-1] :
_name = '' ## root / @ _name = '' ## root / @
@@ -251,6 +250,85 @@ class AkamaiProvider(BaseProvider):
return exists return exists
def _apply(self, plan):
desired = plan.desired
changes = plan.changes
self.log.debug('apply: zone=%s, changes=%d', desired.name, len(changes))
zone_name = desired.name[:-1]
try:
self._dns_client.zone_get(zone_name)
except AkamaiClientException:
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): def _data_for_multiple(self, _type, records):
return { return {
@@ -326,6 +404,7 @@ class AkamaiProvider(BaseProvider):
'target': target, 'target': target,
'weight': weight 'weight': weight
}) })
return { return {
'type': _type, 'type': _type,
'ttl':records['ttl'], 'ttl':records['ttl'],
@@ -341,6 +420,7 @@ class AkamaiProvider(BaseProvider):
'fingerprint': fingerprint, 'fingerprint': fingerprint,
'fingerprint_type': fp_type 'fingerprint_type': fp_type
}) })
return { return {
'type': _type, 'type': _type,
'ttl': records['ttl'], 'ttl': records['ttl'],
@@ -360,84 +440,6 @@ class AkamaiProvider(BaseProvider):
} }
def _apply(self, plan):
desired = plan.desired
changes = plan.changes
self.log.debug('_apply: zone=%s, changes=%d', desired.name, len(changes))
zone_name = desired.name[:-1]
try:
self._dns_client.zone_get(zone_name)
except AkamaiClientException:
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 _params_for_multiple(self, values): def _params_for_multiple(self, values):
return [r for r in values] return [r for r in values]
@@ -456,12 +458,10 @@ class AkamaiProvider(BaseProvider):
rdata = [] rdata = []
for r in values: for r in values:
print(json.dumps(r, indent=4, separators=(',', ': ')))
preference = r['preference'] preference = r['preference']
exchange = r['exchange'] exchange = r['exchange']
record = '{} {}'.format(preference, exchange) record = '{} {}'.format(preference, exchange)
rdata.append(record) rdata.append(record)
return rdata return rdata
@@ -470,16 +470,14 @@ class AkamaiProvider(BaseProvider):
rdata = [] rdata = []
for r in values: for r in values:
order = r['order'] ordr = r['order']
preference = r['preference'] pref = r['preference']
flags = "\"" + r['flags'] + "\"" flg = "\"" + r['flags'] + "\""
service = "\"" + r['service'] + "\"" srvc = "\"" + r['service'] + "\""
regexp = "\"" + r['regexp'] + "\"" rgx = "\"" + r['regexp'] + "\""
repl = r['replacement'] rpl = r['replacement']
record = '{} {} {} {} {} {}'.format(order, preference, flags, service, record = '{} {} {} {} {} {}'.format(ordr, pref, flg, srvc, rgx, rpl)
regexp, repl)
# record = ' '.join([order, preference, flags, service, regexp, repl])
rdata.append(record) rdata.append(record)
return rdata return rdata
@@ -518,15 +516,13 @@ class AkamaiProvider(BaseProvider):
return rdata return rdata
def _build_zone_config(self, zone, _type=None, comment=None, masters=[]): def _build_zone_config(self, zone, _type=None, comment=None, masters=[]):
if _type is None: if _type is None:
_type="primary" _type="primary"
if self._contractId is None: if self._contractId is None:
self._set_default_contractId() raise NameError("contractId not specified to create zone")
return { return {
"zone": zone, "zone": zone,
@@ -535,34 +531,6 @@ class AkamaiProvider(BaseProvider):
"masters": masters "masters": masters
} }
def _set_default_contractId(self):
''' if no contractId is set, but one is required to create a new zone,
this function will try to retrieve any contracts available to the
user, and use the first one it finds
'''
try:
request = self._dns_client.zones_get(self._gid)
response = request.json()
zones = response['zones']
contractId = zones[0]['contractId']
self._contractId = contractId
self.log.info("contractId not specified, using contractId=%s", contractId)
except KeyError:
self.log.debug("_get_default_contractId: key error")
raise
except:
self.log.debug("_get_default_contractId: unable to find a contractId")
raise
return
def _get_values(self, data): def _get_values(self, data):
try: try:
@@ -574,190 +542,9 @@ class AkamaiProvider(BaseProvider):
def _set_full_name(self, name, zone): def _set_full_name(self, name, zone):
name = name + '.' + zone name = name + '.' + zone
if (name[0] == '.'): ## octodns's name for root is ''
## octodns's name for root is ''
if (name[0] == '.'):
name = name[1:] name = name[1:]
return name return name
def _test(self, zone) :
zone_name = zone.name[:len(zone.name)-1]
record_name = "octo.basir-test.com"
record_type = "A"
params = {
"name": "octo.basir-test.com",
"type": "A",
"ttl": 300,
"rdata": [
"10.0.0.2",
"10.0.0.3"
]
}
repl_params = {
"name": "octo.basir-test.com",
"type": "A",
"ttl": 300,
"rdata": [
"99.99.99.99",
"10.0.0.3",
"1.2.3.4"
]
}
print("\n\nRunning test: record get..........\n")
self._test_record_get(zone_name, "test.basir-test.com", record_type)
print("\n\nRunning test: record create..........\n")
self._test_record_create(zone_name, record_name, record_type, params)
print("\n\nRunning test: record replace..........\n")
self._test_record_replace(zone_name, record_name, record_type, repl_params)
print("\n\nRunning test: record delete..........\n")
self._test_record_delete(zone_name, record_name, record_type)
print("\n\nRunning test: zones get..........\n")
self._test_zones_get()
print("\n\nRunning test: zone recordset get..........\n")
self._test_zones_recordset_get(zone_name)
print("\n\nRunning test: Master Zone File get..........\n")
self._test_master_zone_file_get(zone_name)
return
def _test_record_get(self, zone_name, record_name, record_type):
try:
get = self._dns_client.record_get(zone_name, record_name, record_type)
except AkamaiClientException as e:
print ("record get test failed")
print (e.message)
else:
print("record get test result: ")
print(json.dumps(get.json(), indent=4, separators=(',', ': ')))
return
def _test_record_delete(self, zone_name, record_name, record_type):
try:
delete = self._dns_client.record_delete(zone_name, record_name, record_type)
except AkamaiClientException as e:
print("delete failed")
print(e.message)
return
try:
self._dns_client.record_get(zone_name, record_name, record_type)
except AkamaiClientException as e:
print("get on record failed as expected, since record was succesfully deleted")
print ("(Probably Ignore):", e.message)
print ("delete status:", delete.status_code)
else:
print("unexpected condition in test delete")
return
def _test_record_create(self, zone_name, record_name, record_type, params):
try:
create = self._dns_client.record_create(zone_name, record_name, record_type, params)
except AkamaiClientException as e:
print ("create unsuccessful, presumably because it already exists")
print ("(Probably Ignore)", e.message)
else:
print("initial create of", create.json().get("name"), "succesful: ", create.status_code)
return
def _test_record_replace(self, zone_name, record_name, record_type, params):
## create record to be replaced, if it doesn't already exist
try:
old_params = {
"name": record_name,
"type": record_type,
"ttl": 300,
"rdata": [
"10.0.0.2",
"10.0.0.3"
]
}
create = self._dns_client.record_create(zone_name, record_name, record_type, old_params)
except AkamaiClientException as e:
print ("initial create unsuccessful, presumably because it already exists")
print ("(Probably Ignore)", e.message)
else:
print("initial create of record to be replaced", create.json().get("name"), "succesful: ", create.status_code)
## test replace
try:
replace = self._dns_client.record_replace(zone_name, record_name, record_type, params)
except AkamaiClientException as e:
print("replace failed")
print(e.message)
return
else:
try:
record = self._dns_client.record_get(zone_name, record_name, record_type)
except AkamaiClientException as e:
print("retrieval in replacement failed")
print(e.message)
else:
new_data = record.json()
if (new_data != params):
print("replace failed, records don't match")
print("current data:")
print(new_data)
print("expected data:")
print(params)
else:
print("replace succesful")
print("replace status:", replace.status_code)
def _test_zones_get(self):
try:
zonesList = self._dns_client.zones_get()
except AkamaiClientException as e:
print ("zones get test failed")
print (e.message)
else:
print("zones list: ")
print(json.dumps(zonesList.json(), indent=4, separators=(',', ': ')))
return
def _test_zones_recordset_get(self, zone_name):
try:
zoneRecordset = self._dns_client.zone_recordset_get(zone_name)
except AkamaiClientException as e:
print("zone recordset retrieval test failed")
print (e.message)
else:
print("zone recordset: ")
print(json.dumps(zoneRecordset.json(), indent=4, separators=(',', ': ')))
return
def _test_master_zone_file_get(self, zone_name):
try:
mzf = self._dns_client.master_zone_file_get(zone_name)
except AkamaiClientException as e:
print("MZF retrieval test failed")
print (e.message)
else:
print("Master Zone File:")
print(json.dumps(mzf.json(), indent=4, separators=(',', ': ')))
return