mirror of
				https://github.com/github/octodns.git
				synced 2024-05-11 05:55:00 +00:00 
			
		
		
		
	Rename fastdns to edgedns w/backwards compat for now
This commit is contained in:
		@@ -177,7 +177,7 @@ The above command pulled the existing data out of Route53 and placed the results
 | 
			
		||||
| Provider | Requirements | Record Support | Dynamic | Notes |
 | 
			
		||||
|--|--|--|--|--|
 | 
			
		||||
| [AzureProvider](/octodns/provider/azuredns.py) | azure-mgmt-dns | A, AAAA, CAA, CNAME, MX, NS, PTR, SRV, TXT | No | |
 | 
			
		||||
| [Akamai](/octodns/provider/fastdns.py) | edgegrid-python | A, AAAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, SSHFP, TXT | No | |
 | 
			
		||||
| [Akamai](/octodns/provider/edgedns.py) | edgegrid-python | A, AAAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, SSHFP, TXT | No | |
 | 
			
		||||
| [CloudflareProvider](/octodns/provider/cloudflare.py) | | A, AAAA, ALIAS, CAA, CNAME, MX, NS, SPF, SRV, TXT | No | CAA tags restricted |
 | 
			
		||||
| [ConstellixProvider](/octodns/provider/constellix.py) | | A, AAAA, ALIAS (ANAME), CAA, CNAME, MX, NS, PTR, SPF, SRV, TXT | No | CAA tags restricted |
 | 
			
		||||
| [DigitalOceanProvider](/octodns/provider/digitalocean.py) | | A, AAAA, CAA, CNAME, MX, NS, TXT, SRV | No | CAA tags restricted |
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										525
									
								
								octodns/provider/edgedns.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										525
									
								
								octodns/provider/edgedns.py
									
									
									
									
									
										Normal 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 six.moves.urllib.parse 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
 | 
			
		||||
 | 
			
		||||
    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('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
 | 
			
		||||
@@ -5,521 +5,12 @@
 | 
			
		||||
from __future__ import absolute_import, division, print_function, \
 | 
			
		||||
    unicode_literals
 | 
			
		||||
 | 
			
		||||
from requests import Session
 | 
			
		||||
from akamai.edgegrid import EdgeGridAuth
 | 
			
		||||
from six.moves.urllib.parse import urljoin
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
from .edgedns import AkamaiProvider
 | 
			
		||||
from logging import getLogger
 | 
			
		||||
from ..record import Record
 | 
			
		||||
from .base import BaseProvider
 | 
			
		||||
 | 
			
		||||
# Quell unused warning
 | 
			
		||||
AkamaiProvider
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
log = getLogger('octodns.provider.fastdns.AkamaiProvider')
 | 
			
		||||
log.warn('DEPRECATION NOTICE: AkamaiProvider has been moved to '
 | 
			
		||||
         'octodns.provider.fastdns.AkamaiProvider')
 | 
			
		||||
 
 | 
			
		||||
@@ -13,12 +13,13 @@ from six import text_type
 | 
			
		||||
from unittest import TestCase
 | 
			
		||||
 | 
			
		||||
from octodns.record import Record
 | 
			
		||||
from octodns.provider.fastdns import AkamaiProvider
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestFastdnsProvider(TestCase):
 | 
			
		||||
class TestEdgeDnsProvider(TestCase):
 | 
			
		||||
    expected = Zone('unit.tests.', [])
 | 
			
		||||
    source = YamlProvider('test', join(dirname(__file__), 'config'))
 | 
			
		||||
    source.populate(expected)
 | 
			
		||||
@@ -71,7 +72,7 @@ class TestFastdnsProvider(TestCase):
 | 
			
		||||
        # No diffs == no changes
 | 
			
		||||
        with requests_mock() as mock:
 | 
			
		||||
 | 
			
		||||
            with open('tests/fixtures/fastdns-records.json') as fh:
 | 
			
		||||
            with open('tests/fixtures/edgedns-records.json') as fh:
 | 
			
		||||
                mock.get(ANY, text=fh.read())
 | 
			
		||||
 | 
			
		||||
            zone = Zone('unit.tests.', [])
 | 
			
		||||
@@ -95,7 +96,7 @@ class TestFastdnsProvider(TestCase):
 | 
			
		||||
        # tests create update delete through previous state config json
 | 
			
		||||
        with requests_mock() as mock:
 | 
			
		||||
 | 
			
		||||
            with open('tests/fixtures/fastdns-records-prev.json') as fh:
 | 
			
		||||
            with open('tests/fixtures/edgedns-records-prev.json') as fh:
 | 
			
		||||
                mock.get(ANY, text=fh.read())
 | 
			
		||||
 | 
			
		||||
            plan = provider.plan(self.expected)
 | 
			
		||||
@@ -108,7 +109,7 @@ class TestFastdnsProvider(TestCase):
 | 
			
		||||
 | 
			
		||||
        # 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:
 | 
			
		||||
            with open('tests/fixtures/edgedns-records-prev-other.json') as fh:
 | 
			
		||||
                mock.get(ANY, status_code=404)
 | 
			
		||||
 | 
			
		||||
            plan = provider.plan(self.expected)
 | 
			
		||||
@@ -121,7 +122,7 @@ class TestFastdnsProvider(TestCase):
 | 
			
		||||
 | 
			
		||||
        # 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:
 | 
			
		||||
            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")
 | 
			
		||||
@@ -149,3 +150,9 @@ class TestFastdnsProvider(TestCase):
 | 
			
		||||
            except NameError as e:
 | 
			
		||||
                expected = "contractId not specified to create zone"
 | 
			
		||||
                self.assertEquals(text_type(e), expected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDeprecatedAkamaiProvider(TestCase):
 | 
			
		||||
 | 
			
		||||
    def test_equivilent(self):
 | 
			
		||||
        self.assertEquals(LegacyAkamaiProvider, AkamaiProvider)
 | 
			
		||||
		Reference in New Issue
	
	Block a user