Source code for libcloud.dns.drivers.auroradns

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
AuroraDNS DNS Driver

import base64
import json
import hmac
import datetime

from hashlib import sha256

from libcloud.utils.py3 import httplib
from libcloud.utils.py3 import b

from libcloud.common.base import ConnectionUserAndKey, JsonResponse

from libcloud.common.types import InvalidCredsError, ProviderError
from libcloud.common.types import LibcloudError

from libcloud.dns.base import DNSDriver, Zone, Record
from libcloud.dns.types import RecordType, ZoneDoesNotExistError
from libcloud.dns.types import ZoneAlreadyExistsError, RecordDoesNotExistError


# Default TTL required by libcloud, but doesn't do anything in AuroraDNS

VALID_RECORD_PARAMS_EXTRA = ['ttl', 'prio', 'health_check_id', 'disabled']

[docs]class AuroraDNSHealthCheckType(object): """ Healthcheck type. """ HTTP = 'HTTP' HTTPS = 'HTTPS' TCP = 'TCP'
[docs]class HealthCheckError(LibcloudError): error_type = 'HealthCheckError' def __init__(self, value, driver, health_check_id): self.health_check_id = health_check_id super(HealthCheckError, self).__init__(value=value, driver=driver) def __str__(self): return self.__repr__() def __repr__(self): return ('<%s in %s, health_check_id=%s, value=%s>' % (self.error_type, repr(self.driver), self.health_check_id, self.value))
[docs]class HealthCheckDoesNotExistError(HealthCheckError): error_type = 'HealthCheckDoesNotExistError'
[docs]class AuroraDNSHealthCheck(object): """ AuroraDNS Healthcheck resource. """ def __init__(self, id, type, hostname, ipaddress, port, interval, path, threshold, health, enabled, zone, driver, extra=None): """ :param id: Healthcheck id :type id: ``str`` :param hostname: Hostname or FQDN of the target :type hostname: ``str`` :param ipaddress: IPv4 or IPv6 address of the target :type ipaddress: ``str`` :param port: The port on the target to monitor :type port: ``int`` :param interval: The interval of the health check :type interval: ``int`` :param path: The path to monitor on the target :type path: ``str`` :param threshold: The threshold of before marking a check as failed :type threshold: ``int`` :param health: The current health of the health check :type health: ``bool`` :param enabled: If the health check is currently enabled :type enabled: ``bool`` :param zone: Zone instance. :type zone: :class:`Zone` :param driver: DNSDriver instance. :type driver: :class:`DNSDriver` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` """ = str(id) if id else None self.type = type self.hostname = hostname self.ipaddress = ipaddress self.port = int(port) if port else None self.interval = int(interval) self.path = path self.threshold = int(threshold) = bool(health) self.enabled = bool(enabled) = zone self.driver = driver self.extra = extra or {}
[docs] def update(self, type=None, hostname=None, ipaddress=None, port=None, interval=None, path=None, threshold=None, enabled=None, extra=None): return self.driver.ex_update_healthcheck(healthcheck=self, type=type, hostname=hostname, ipaddress=ipaddress, port=port, path=path, interval=interval, threshold=threshold, enabled=enabled, extra=extra)
[docs] def delete(self): return self.driver.ex_delete_healthcheck(healthcheck=self)
def __repr__(self): return ('<AuroraDNSHealthCheck: zone=%s, id=%s, type=%s, hostname=%s, ' 'ipaddress=%s, port=%d, interval=%d, health=%s, provider=%s' '...>' % (,, self.type, self.hostname, self.ipaddress, self.port, self.interval,,
[docs]class AuroraDNSResponse(JsonResponse):
[docs] def success(self): return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
[docs] def parse_error(self): status = int(self.status) error = {'driver': self, 'value': ''} if status == httplib.UNAUTHORIZED: error['value'] = 'Authentication failed' raise InvalidCredsError(**error) elif status == httplib.FORBIDDEN: error['value'] = 'Authorization failed' error['http_status'] = status raise ProviderError(**error) elif status == httplib.NOT_FOUND: context = self.connection.context if context['resource'] == 'zone': error['zone_id'] = context['id'] raise ZoneDoesNotExistError(**error) elif context['resource'] == 'record': error['record_id'] = context['id'] raise RecordDoesNotExistError(**error) elif context['resource'] == 'healthcheck': error['health_check_id'] = context['id'] raise HealthCheckDoesNotExistError(**error) elif status == httplib.CONFLICT: context = self.connection.context if context['resource'] == 'zone': error['zone_id'] = context['id'] raise ZoneAlreadyExistsError(**error) elif status == httplib.BAD_REQUEST: context = self.connection.context body = self.parse_body() raise ProviderError(value=body['errormsg'], http_code=status, driver=self)
[docs]class AuroraDNSConnection(ConnectionUserAndKey): host = API_HOST responseCls = AuroraDNSResponse
[docs] def calculate_auth_signature(self, secret_key, method, url, timestamp): b64_hmac = base64.b64encode(, b(method) + b(url) + b(timestamp), digestmod=sha256).digest() ) return b64_hmac.decode('utf-8')
[docs] def gen_auth_header(self, api_key, secret_key, method, url, timestamp): signature = self.calculate_auth_signature(secret_key, method, url, timestamp) auth_b64 = base64.b64encode(b('%s:%s' % (api_key, signature))) return 'AuroraDNSv1 %s' % (auth_b64.decode('utf-8'))
[docs] def request(self, action, params=None, data='', headers=None, method='GET'): if not headers: headers = {} if not params: params = {} if method in ("POST", "PUT"): headers = {'Content-Type': 'application/json; charset=UTF-8'} t = datetime.datetime.utcnow() timestamp = t.strftime('%Y%m%dT%H%M%SZ') headers['X-AuroraDNS-Date'] = timestamp headers['Authorization'] = self.gen_auth_header(self.user_id, self.key, method, action, timestamp) return super(AuroraDNSConnection, self).request(action=action, params=params, data=data, method=method, headers=headers)
[docs]class AuroraDNSDriver(DNSDriver): name = 'AuroraDNS' website = '' connectionCls = AuroraDNSConnection RECORD_TYPE_MAP = { RecordType.A: 'A', RecordType.AAAA: 'AAAA', RecordType.CNAME: 'CNAME', RecordType.MX: 'MX', RecordType.NS: 'NS', RecordType.SOA: 'SOA', RecordType.SRV: 'SRV', RecordType.TXT: 'TXT', RecordType.DS: 'DS', RecordType.PTR: 'PTR', RecordType.SSHFP: 'SSHFP', RecordType.TLSA: 'TLSA' } HEALTHCHECK_TYPE_MAP = { AuroraDNSHealthCheckType.HTTP: 'HTTP', AuroraDNSHealthCheckType.HTTPS: 'HTTPS', AuroraDNSHealthCheckType.TCP: 'TCP' }
[docs] def iterate_zones(self): res = self.connection.request('/zones') for zone in res.parse_body(): yield self.__res_to_zone(zone)
[docs] def iterate_records(self, zone): self.connection.set_context({'resource': 'zone', 'id':}) res = self.connection.request('/zones/%s/records' % for record in res.parse_body(): yield self.__res_to_record(zone, record)
[docs] def get_zone(self, zone_id): self.connection.set_context({'resource': 'zone', 'id': zone_id}) res = self.connection.request('/zones/%s' % zone_id) zone = res.parse_body() return self.__res_to_zone(zone)
[docs] def get_record(self, zone_id, record_id): self.connection.set_context({'resource': 'record', 'id': record_id}) res = self.connection.request('/zones/%s/records/%s' % (zone_id, record_id)) record = res.parse_body() zone = self.get_zone(zone_id) return self.__res_to_record(zone, record)
[docs] def create_zone(self, domain, type='master', ttl=None, extra=None): self.connection.set_context({'resource': 'zone', 'id': domain}) res = self.connection.request('/zones', method='POST', data=json.dumps({'name': domain})) zone = res.parse_body() return self.__res_to_zone(zone)
[docs] def create_record(self, name, zone, type, data, extra=None): if name is None: name = "" rdata = { 'name': name, 'type': self.RECORD_TYPE_MAP[type], 'content': data } rdata = self.__merge_extra_data(rdata, extra) if 'ttl' not in rdata: rdata['ttl'] = DEFAULT_ZONE_TTL self.connection.set_context({'resource': 'zone', 'id':}) res = self.connection.request('/zones/%s/records' %, method='POST', data=json.dumps(rdata)) record = res.parse_body() return self.__res_to_record(zone, record)
[docs] def delete_zone(self, zone): self.connection.set_context({'resource': 'zone', 'id':}) self.connection.request('/zones/%s' %, method='DELETE') return True
[docs] def delete_record(self, record): self.connection.set_context({'resource': 'record', 'id':}) self.connection.request('/zones/%s/records/%s' % (,, method='DELETE') return True
[docs] def list_record_types(self): types = [] for record_type in self.RECORD_TYPE_MAP.keys(): types.append(record_type) return types
[docs] def update_record(self, record, name, type, data, extra=None): rdata = {} if name is not None: rdata['name'] = name if type is not None: rdata['type'] = self.RECORD_TYPE_MAP[type] if data is not None: rdata['content'] = data rdata = self.__merge_extra_data(rdata, extra) self.connection.set_context({'resource': 'record', 'id':}) self.connection.request('/zones/%s/records/%s' % (,, method='PUT', data=json.dumps(rdata)) return self.get_record(,
[docs] def ex_list_healthchecks(self, zone): """ List all Health Checks in a zone. :param zone: Zone to list health checks for. :type zone: :class:`Zone` :return: ``list`` of :class:`AuroraDNSHealthCheck` """ healthchecks = [] self.connection.set_context({'resource': 'zone', 'id':}) res = self.connection.request('/zones/%s/health_checks' % for healthcheck in res.parse_body(): healthchecks.append(self.__res_to_healthcheck(zone, healthcheck)) return healthchecks
[docs] def ex_get_healthcheck(self, zone, health_check_id): """ Get a single Health Check from a zone :param zone: Zone in which the health check is :type zone: :class:`Zone` :param health_check_id: ID of the required health check :type health_check_id: ``str`` :return: :class:`AuroraDNSHealthCheck` """ self.connection.set_context({'resource': 'healthcheck', 'id': health_check_id}) res = self.connection.request('/zones/%s/health_checks/%s' % (, health_check_id)) check = res.parse_body() return self.__res_to_healthcheck(zone, check)
[docs] def ex_create_healthcheck(self, zone, type, hostname, port, path, interval, threshold, ipaddress=None, enabled=True, extra=None): """ Create a new Health Check in a zone :param zone: Zone in which the health check should be created :type zone: :class:`Zone` :param type: The type of health check to be created :type type: :class:`AuroraDNSHealthCheckType` :param hostname: The hostname of the target to monitor :type hostname: ``str`` :param port: The port of the target to monitor. E.g. 80 for HTTP :type port: ``int`` :param path: The path of the target to monitor. Only used by HTTP at this moment. Usually this is simple /. :type path: ``str`` :param interval: The interval of checks. 10, 30 or 60 seconds. :type interval: ``int`` :param threshold: The threshold of failures before the healthcheck is marked as failed. :type threshold: ``int`` :param ipaddress: (optional) The IP Address of the target to monitor. You can pass a empty string if this is not required. :type ipaddress: ``str`` :param enabled: (optional) If this healthcheck is enabled to run :type enabled: ``bool`` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :return: :class:`AuroraDNSHealthCheck` """ cdata = { 'type': self.HEALTHCHECK_TYPE_MAP[type], 'hostname': hostname, 'ipaddress': ipaddress, 'port': int(port), 'interval': int(interval), 'path': path, 'threshold': int(threshold), 'enabled': enabled } self.connection.set_context({'resource': 'zone', 'id':}) res = self.connection.request('/zones/%s/health_checks' %, method='POST', data=json.dumps(cdata)) healthcheck = res.parse_body() return self.__res_to_healthcheck(zone, healthcheck)
[docs] def ex_update_healthcheck(self, healthcheck, type=None, hostname=None, ipaddress=None, port=None, path=None, interval=None, threshold=None, enabled=None, extra=None): """ Update an existing Health Check :param zone: The healthcheck which has to be updated :type zone: :class:`AuroraDNSHealthCheck` :param type: (optional) The type of health check to be created :type type: :class:`AuroraDNSHealthCheckType` :param hostname: (optional) The hostname of the target to monitor :type hostname: ``str`` :param ipaddress: (optional) The IP Address of the target to monitor. You can pass a empty string if this is not required. :type ipaddress: ``str`` :param port: (optional) The port of the target to monitor. E.g. 80 for HTTP :type port: ``int`` :param path: (optional) The path of the target to monitor. Only used by HTTP at this moment. Usually just '/'. :type path: ``str`` :param interval: (optional) The interval of checks. 10, 30 or 60 seconds. :type interval: ``int`` :param threshold: (optional) The threshold of failures before the healthcheck is marked as failed. :type threshold: ``int`` :param enabled: (optional) If this healthcheck is enabled to run :type enabled: ``bool`` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :return: :class:`AuroraDNSHealthCheck` """ cdata = {} if type is not None: cdata['type'] = self.HEALTHCHECK_TYPE_MAP[type] if hostname is not None: cdata['hostname'] = hostname if ipaddress is not None: if len(ipaddress) == 0: cdata['ipaddress'] = None else: cdata['ipaddress'] = ipaddress if port is not None: cdata['port'] = int(port) if path is not None: cdata['path'] = path if interval is not None: cdata['interval'] = int(interval) if threshold is not None: cdata['threshold'] = threshold if enabled is not None: cdata['enabled'] = bool(enabled) self.connection.set_context({'resource': 'healthcheck', 'id':}) self.connection.request('/zones/%s/health_checks/%s' % (,, method='PUT', data=json.dumps(cdata)) return self.ex_get_healthcheck(,
[docs] def ex_delete_healthcheck(self, healthcheck): """ Remove an existing Health Check :param zone: The healthcheck which has to be removed :type zone: :class:`AuroraDNSHealthCheck` """ self.connection.set_context({'resource': 'healthcheck', 'id':}) self.connection.request('/zones/%s/health_checks/%s' % (,, method='DELETE') return True
def __res_to_record(self, zone, record): if len(record['name']) == 0: name = None else: name = record['name'] extra = {} extra['created'] = record['created'] extra['modified'] = record['modified'] extra['disabled'] = record['disabled'] extra['ttl'] = record['ttl'] extra['priority'] = record['prio'] return Record(id=record['id'], name=name, type=record['type'], data=record['content'], zone=zone, driver=self.connection.driver, ttl=record['ttl'], extra=extra) def __res_to_zone(self, zone): return Zone(id=zone['id'], domain=zone['name'], type=DEFAULT_ZONE_TYPE, ttl=DEFAULT_ZONE_TTL, driver=self.connection.driver, extra={'created': zone['created'], 'servers': zone['servers'], 'account_id': zone['account_id'], 'cluster_id': zone['cluster_id']}) def __res_to_healthcheck(self, zone, healthcheck): return AuroraDNSHealthCheck(id=healthcheck['id'], type=healthcheck['type'], hostname=healthcheck['hostname'], ipaddress=healthcheck['ipaddress'], health=healthcheck['health'], threshold=healthcheck['threshold'], path=healthcheck['path'], interval=healthcheck['interval'], port=healthcheck['port'], enabled=healthcheck['enabled'], zone=zone, driver=self.connection.driver) def __merge_extra_data(self, rdata, extra): if extra is not None: for param in VALID_RECORD_PARAMS_EXTRA: if param in extra: rdata[param] = extra[param] return rdata