http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/nsone.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/nsone.py b/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/nsone.py deleted file mode 100644 index 06297e6..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/nsone.py +++ /dev/null @@ -1,344 +0,0 @@ -import sys -try: - import simplejson as json -except ImportError: - import json - -from libcloud.dns.types import Provider, ZoneDoesNotExistError, \ - ZoneAlreadyExistsError, RecordDoesNotExistError, RecordAlreadyExistsError -from libcloud.utils.py3 import httplib -from libcloud.dns.base import DNSDriver, Zone, Record, RecordType -from libcloud.common.nsone import NsOneConnection, NsOneResponse, \ - NsOneException - -__all__ = [ - 'NsOneDNSDriver' -] - - -class NsOneDNSResponse(NsOneResponse): - pass - - -class NsOneDNSConnection(NsOneConnection): - responseCls = NsOneDNSResponse - - -class NsOneDNSDriver(DNSDriver): - name = 'NS1 DNS' - website = 'https://ns1.com' - type = Provider.NSONE - connectionCls = NsOneDNSConnection - - RECORD_TYPE_MAP = { - RecordType.A: 'A', - RecordType.AAAA: 'AAAA', - RecordType.CNAME: 'CNAME', - RecordType.MX: 'MX', - RecordType.NS: 'NS', - RecordType.PTR: 'PTR', - RecordType.SOA: 'SOA', - RecordType.SRV: 'SRV', - RecordType.TXT: 'TXT' - } - - def list_zones(self): - action = '/v1/zones' - response = self.connection.request(action=action, method='GET') - zones = self._to_zones(items=response.parse_body()) - - return zones - - def get_zone(self, zone_id): - """ - :param zone_id: Zone domain name (e.g. example.com) - :return: :class:`Zone` - """ - action = '/v1/zones/%s' % zone_id - try: - response = self.connection.request(action=action, method='GET') - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'zone not found': - raise ZoneDoesNotExistError(value=e.message, driver=self, - zone_id=zone_id) - else: - raise e - zone = self._to_zone(response.objects[0]) - - return zone - - def create_zone(self, domain, type='master', ttl=None, extra=None): - """ - :param domain: Zone domain name (e.g. example.com) - :type domain: ``str`` - - :param type: Zone type (This is not really used. See API docs for extra - parameters) - :type type: ``str`` - - :param ttl: TTL for new records (This is used through the extra param) - :type ttl: ``int`` - - :param extra: Extra attributes that are specific to the driver - such as ttl. - :type extra: ``dict`` - - :rtype: :class:`Zone` - """ - action = '/v1/zones/%s' % domain - raw_data = {'zone': domain} - if extra is not None: - raw_data.update(extra) - post_data = json.dumps(raw_data) - try: - response = self.connection.request(action=action, method='PUT', - data=post_data) - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'zone already exists': - raise ZoneAlreadyExistsError(value=e.message, driver=self, - zone_id=domain) - else: - raise e - - zone = self._to_zone(response.objects[0]) - - return zone - - def delete_zone(self, zone): - """ - :param zone: Zone to be deleted. - :type zone: :class:`Zone` - - :return: Boolean - """ - action = '/v1/zones/%s' % zone.domain - """zones_list = self.list_zones() - if not self.ex_zone_exists(zone_id=zone.id, zones_list=zones_list): - raise ZoneDoesNotExistError(value='', driver=self, zone_id=zone.id) - """ - try: - response = self.connection.request(action=action, method='DELETE') - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'zone not found': - raise ZoneDoesNotExistError(value=e.message, driver=self, - zone_id=zone.id) - else: - raise e - - return response.status == httplib.OK - - def list_records(self, zone): - """ - :param zone: Zone to list records for. - :type zone: :class:`Zone` - - :return: ``list`` of :class:`Record` - """ - action = '/v1/zones/%s' % zone.domain - try: - response = self.connection.request(action=action, method='GET') - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'zone not found': - raise ZoneDoesNotExistError(value=e.message, driver=self, - zone_id=zone.id) - else: - raise e - records = self._to_records(items=response.parse_body()['records'], - zone=zone) - - return records - - def get_record(self, zone_id, record_id): - """ - :param zone_id: The id of the zone where to search for - the record (e.g. example.com) - :type zone_id: ``str`` - :param record_id: The type of record to search for - (e.g. A, AAA, MX etc) - - :return: :class:`Record` - """ - action = '/v1/zones/%s/%s/%s' % (zone_id, zone_id, record_id) - try: - response = self.connection.request(action=action, method='GET') - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'record not found': - raise RecordDoesNotExistError(value=e.message, driver=self, - record_id=record_id) - else: - raise e - zone = self.get_zone(zone_id=zone_id) - record = self._to_record(item=response.parse_body(), zone=zone) - - return record - - def delete_record(self, record): - """ - :param record: Record to delete. - :type record: :class:`Record` - - :return: Boolean - """ - action = '/v1/zones/%s/%s/%s' % (record.zone.domain, record.name, - record.type) - try: - response = self.connection.request(action=action, method='DELETE') - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'record not found': - raise RecordDoesNotExistError(value=e.message, driver=self, - record_id=record.id) - else: - raise e - - return response.status == httplib.OK - - def create_record(self, name, zone, type, data, extra=None): - """ - :param name: Name of the record to create (e.g. foo). - :type name: ``str`` - :param zone: Zone where the record should be created. - :type zone: :class:`Zone` - :param type: Type of record (e.g. A, MX etc) - :type type: ``str`` - :param data: Data of the record (e.g. 127.0.0.1 for the A record) - :type data: ``str`` - :param extra: Extra data needed to create different types of records - :type extra: ``dict`` - :return: :class:`Record` - """ - action = '/v1/zones/%s/%s/%s' % (zone.domain, '%s.%s' % - (name, zone.domain), type) - raw_data = { - "answers": [ - { - "answer": [ - data - ], } - ], - "type": type, - "domain": '%s.%s' % (name, zone.domain), - "zone": zone.domain - } - if extra is not None and extra.get('answers'): - raw_data['answers'] = extra.get('answers') - post_data = json.dumps(raw_data) - try: - response = self.connection.request(action=action, method='PUT', - data=post_data) - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'record already exists': - raise RecordAlreadyExistsError(value=e.message, driver=self, - record_id='') - else: - raise e - record = self._to_record(item=response.parse_body(), zone=zone) - - return record - - def update_record(self, record, name, type, data, extra=None): - """ - :param record: Record to update - :type record: :class:`Record` - :param name: Name of the record to update (e.g. foo). - :type name: ``str`` - :param type: Type of record (e.g. A, MX etc) - :type type: ``str`` - :param data: Data of the record (e.g. 127.0.0.1 for the A record) - :type data: ``str`` - :param extra: Extra data needed to create different types of records - :type extra: ``dict`` - :return: :class:`Record` - """ - zone = record.zone - action = '/v1/zones/%s/%s/%s' % (zone.domain, '%s.%s' % - (name, zone.domain), type) - raw_data = { - "answers": [ - { - "answer": [ - data - ], } - ] - } - if extra is not None and extra.get('answers'): - raw_data['answers'] = extra.get('answers') - post_data = json.dumps(raw_data) - try: - response = self.connection.request(action=action, data=post_data, - method='POST') - except NsOneException: - e = sys.exc_info()[1] - if e.message == 'record does not exist': - raise RecordDoesNotExistError(value=e.message, driver=self, - id=record.id) - else: - raise e - record = self._to_record(item=response.parse_body(), zone=zone) - - return record - - def ex_zone_exists(self, zone_id, zones_list): - """ - Function to check if a `Zone` object exists. - :param zone_id: ID of the `Zone` object. - :type zone_id: ``str`` - - :param zones_list: A list containing `Zone` objects. - :type zones_list: ``list``. - - :rtype: Returns `True` or `False`. - """ - zone_ids = [] - for zone in zones_list: - zone_ids.append(zone.id) - - return zone_id in zone_ids - - def _to_zone(self, item): - common_attr = ['zone', 'id', 'type'] - extra = {} - for key in item.keys(): - if key not in common_attr: - extra[key] = item.get(key) - - zone = Zone(domain=item['zone'], id=item['id'], type=item.get('type'), - extra=extra, ttl=extra.get('ttl'), driver=self) - - return zone - - def _to_zones(self, items): - zones = [] - for item in items: - zones.append(self._to_zone(item)) - - return zones - - def _to_record(self, item, zone): - common_attr = ['id', 'short_answers', 'answers', 'domain', 'type'] - extra = {} - for key in item.keys(): - if key not in common_attr: - extra[key] = item.get(key) - if item.get('answers') is not None: - data = item.get('answers')[0]['answer'] - else: - data = item.get('short_answers') - record = Record(id=item['id'], name=item['domain'], type=item['type'], - data=data, zone=zone, driver=self, - extra=extra) - - return record - - def _to_records(self, items, zone): - records = [] - for item in items: - records.append(self._to_record(item, zone)) - - return records
http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/pointdns.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/pointdns.py b/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/pointdns.py deleted file mode 100644 index 1523bb9..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/pointdns.py +++ /dev/null @@ -1,791 +0,0 @@ -# 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 -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Point DNS Driver -""" - -__all__ = [ - 'PointDNSException', - 'Redirect', - 'MailRedirect', - 'PointDNSDriver' -] - -import sys - -try: - import simplejson as json -except ImportError: - import json - -from libcloud.utils.py3 import httplib -from libcloud.common.types import ProviderError -from libcloud.common.types import MalformedResponseError -from libcloud.common.pointdns import PointDNSConnection -from libcloud.common.exceptions import BaseHTTPError -from libcloud.dns.types import Provider, RecordType -from libcloud.dns.types import ZoneDoesNotExistError -from libcloud.dns.types import RecordDoesNotExistError -from libcloud.dns.base import DNSDriver, Zone, Record - - -class PointDNSException(ProviderError): - - def __init__(self, value, http_code, driver=None): - super(PointDNSException, self).__init__(value=value, - http_code=http_code, - driver=driver) - self.args = (http_code, value) - - -class Redirect(object): - """ - Point DNS redirect. - """ - - def __init__(self, id, name, data, type, driver, zone, iframe=None, - query=False): - """ - :param id: Redirect id. - :type id: ``str`` - - :param name: The FQDN for the record. - :type name: ``str`` - - :param data: The data field. (redirect_to) - :type data: ``str`` - - :param type: The type of redirects 301, 302 or 0 for iframes. - :type type: ``str`` - - :param driver: DNSDriver instance. - :type driver: :class:`DNSDriver` - - :param zone: Zone where redirect belongs. - :type zone: :class:`Zone` - - :param iframe: Title of iframe (optional). - :type iframe: ``str`` - - :param query: boolean Information about including query string when - redirecting. (optional). - :type query: ``bool`` - """ - self.id = str(id) if id else None - self.name = name - self.data = data - self.type = str(type) if type else None - self.driver = driver - self.zone = zone - self.iframe = iframe - self.query = query - - def update(self, data, name=None, type=None, iframe=None, query=None): - return self.driver.ex_update_redirect(redirect=self, name=name, - data=data, type=type, - iframe=iframe, query=query) - - def delete(self): - return self.driver.ex_delete_redirect(redirect=self) - - def __repr__(self): - return ('<PointDNSRedirect: name=%s, data=%s, type=%s ...>' % - (self.name, self.data, self.type)) - - -class MailRedirect(object): - """ - Point DNS mail redirect. - """ - - def __init__(self, id, source, destination, zone, driver): - """ - :param id: MailRedirect id. - :type id: ``str`` - - :param source: The source address of mail redirect. - :type source: ``str`` - - :param destination: The destination address of mail redirect. - :type destination: ``str`` - - :param zone: Zone where mail redirect belongs. - :type zone: :class:`Zone` - - :param driver: DNSDriver instance. - :type driver: :class:`DNSDriver` - """ - self.id = str(id) if id else None - self.source = source - self.destination = destination - self.zone = zone - self.driver = driver - - def update(self, destination, source=None): - return self.driver.ex_update_mail_redirect(mail_r=self, - destination=destination, - source=None) - - def delete(self): - return self.driver.ex_delete_mail_redirect(mail_r=self) - - def __repr__(self): - return ('<PointDNSMailRedirect: source=%s, destination=%s,zone=%s ...>' - % (self.source, self.destination, self.zone.id)) - - -class PointDNSDriver(DNSDriver): - type = Provider.POINTDNS - name = 'Point DNS' - website = 'https://pointhq.com/' - connectionCls = PointDNSConnection - - RECORD_TYPE_MAP = { - RecordType.A: 'A', - RecordType.AAAA: 'AAAA', - RecordType.ALIAS: 'ALIAS', - RecordType.CNAME: 'CNAME', - RecordType.MX: 'MX', - RecordType.NS: 'NS', - RecordType.PTR: 'PTR', - RecordType.SRV: 'SRV', - RecordType.SSHFP: 'SSHFP', - RecordType.TXT: 'TXT' - } - - def list_zones(self): - """ - Return a list of zones. - - :return: ``list`` of :class:`Zone` - """ - response = self.connection.request('/zones') - zones = self._to_zones(response.object) - return zones - - def list_records(self, zone): - """ - Return a list of records for the provided zone. - - :param zone: Zone to list records for. - :type zone: :class:`Zone` - - :return: ``list`` of :class:`Record` - """ - response = self.connection.request('/zones/%s/records' % zone.id) - records = self._to_records(response.object, zone) - return records - - def get_zone(self, zone_id): - """ - Return a Zone instance. - - :param zone_id: ID of the required zone - :type zone_id: ``str`` - - :rtype: :class:`Zone` - """ - try: - response = self.connection.request('/zones/%s' % zone_id) - except MalformedResponseError: - e = sys.exc_info()[1] - if e.body == 'Not found': - raise ZoneDoesNotExistError(driver=self, - value="The zone doesn't exists", - zone_id=zone_id) - raise e - - zone = self._to_zone(response.object) - return zone - - def get_record(self, zone_id, record_id): - """ - Return a Record instance. - - :param zone_id: ID of the required zone - :type zone_id: ``str`` - - :param record_id: ID of the required record - :type record_id: ``str`` - - :rtype: :class:`Record` - """ - try: - response = self.connection.request('/zones/%s/records/%s' % - (zone_id, record_id)) - except MalformedResponseError: - e = sys.exc_info()[1] - if e.body == 'Not found': - raise RecordDoesNotExistError(value="Record doesn't exists", - driver=self, - record_id=record_id) - raise e - - record = self._to_record(response.object, zone_id=zone_id) - return record - - def create_zone(self, domain, type='master', ttl=None, extra=None): - """ - Create a new zone. - - :param domain: Zone domain name (e.g. example.com) - :type domain: ``str`` - - :param type: Zone type (All zones are master by design). - :type type: ``str`` - - :param ttl: TTL for new records. (optional) - :type ttl: ``int`` - - :param extra: Extra attributes (driver specific). (optional) - :type extra: ``dict`` - - :rtype: :class:`Zone` - """ - r_json = {'name': domain} - if ttl is not None: - r_json['ttl'] = ttl - if extra is not None: - r_json.update(extra) - r_data = json.dumps({'zone': r_json}) - try: - response = self.connection.request('/zones', method='POST', - data=r_data) - except BaseHTTPError: - e = sys.exc_info()[1] - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - zone = self._to_zone(response.object) - return zone - - def create_record(self, name, zone, type, data, extra=None): - """ - Create a new record. - - :param name: Record name without the domain name (e.g. www). - Note: If you want to create a record for a base domain - name, you should specify empty string ('') for this - argument. - :type name: ``str`` - - :param zone: Zone where the requested record is created. - :type zone: :class:`Zone` - - :param type: DNS record type (A, AAAA, ...). - :type type: :class:`RecordType` - - :param data: Data for the record (depends on the record type). - :type data: ``str`` - - :param extra: Extra attributes (driver specific). (optional) - :type extra: ``dict`` - - :rtype: :class:`Record` - """ - r_json = {'name': name, 'data': data, 'record_type': type} - if extra is not None: - r_json.update(extra) - r_data = json.dumps({'zone_record': r_json}) - try: - response = self.connection.request('/zones/%s/records' % zone.id, - method='POST', data=r_data) - except BaseHTTPError: - e = sys.exc_info()[1] - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - record = self._to_record(response.object, zone=zone) - return record - - def update_zone(self, zone, domain, type='master', ttl=None, extra=None): - """ - Update en existing zone. - - :param zone: Zone to update. - :type zone: :class:`Zone` - - :param domain: Zone domain name (e.g. example.com) - :type domain: ``str`` - - :param type: Zone type (All zones are master by design). - :type type: ``str`` - - :param ttl: TTL for new records. (optional) - :type ttl: ``int`` - - :param extra: Extra attributes (group, user-id). (optional) - :type extra: ``dict`` - - :rtype: :class:`Zone` - """ - r_json = {'name': domain} - if extra is not None: - r_json.update(extra) - r_data = json.dumps({'zone': r_json}) - try: - response = self.connection.request('/zones/%s' % zone.id, - method='PUT', data=r_data) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise ZoneDoesNotExistError(value="Zone doesn't exists", - driver=self, - zone_id=zone.id) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - zone = self._to_zone(response.object) - return zone - - def update_record(self, record, name, type, data, extra=None): - """ - Update an existing record. - - :param record: Record to update. - :type record: :class:`Record` - - :param name: Record name without the domain name (e.g. www). - Note: If you want to create a record for a base domain - name, you should specify empty string ('') for this - argument. - :type name: ``str`` - - :param type: DNS record type (A, AAAA, ...). - :type type: :class:`RecordType` - - :param data: Data for the record (depends on the record type). - :type data: ``str`` - - :param extra: (optional) Extra attributes (driver specific). - :type extra: ``dict`` - - :rtype: :class:`Record` - """ - zone = record.zone - r_json = {'name': name, 'data': data, 'record_type': type} - if extra is not None: - r_json.update(extra) - r_data = json.dumps({'zone_record': r_json}) - try: - response = self.connection.request('/zones/%s/records/%s' % - (zone.id, record.id), - method='PUT', data=r_data) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise RecordDoesNotExistError(value="Record doesn't exists", - driver=self, - record_id=record.id) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - record = self._to_record(response.object, zone=zone) - return record - - def delete_zone(self, zone): - """ - Delete a zone. - - Note: This will delete all the records belonging to this zone. - - :param zone: Zone to delete. - :type zone: :class:`Zone` - - :rtype: ``bool`` - """ - try: - self.connection.request('/zones/%s' % zone.id, method='DELETE') - except MalformedResponseError: - e = sys.exc_info()[1] - if e.body == 'Not found': - raise ZoneDoesNotExistError(driver=self, - value="The zone doesn't exists", - zone_id=zone.id) - raise e - return True - - def delete_record(self, record): - """ - Delete a record. - - :param record: Record to delete. - :type record: :class:`Record` - - :rtype: ``bool`` - """ - zone_id = record.zone.id - record_id = record.id - try: - self.connection.request('/zones/%s/records/%s' % (zone_id, - record_id), - method='DELETE') - except MalformedResponseError: - e = sys.exc_info()[1] - if e.body == 'Not found': - raise RecordDoesNotExistError(value="Record doesn't exists", - driver=self, - record_id=record_id) - raise e - return True - - def ex_list_redirects(self, zone): - """ - :param zone: Zone to list redirects for. - :type zone: :class:`Zone` - - :rtype: ``list`` of :class:`Record` - """ - response = self.connection.request('/zones/%s/redirects' % zone.id) - redirects = self._to_redirects(response.object, zone) - return redirects - - def ex_list_mail_redirects(self, zone): - """ - :param zone: Zone to list redirects for. - :type zone: :class:`Zone` - - :rtype: ``list`` of :class:`MailRedirect` - """ - response = self.connection.request('/zones/%s/mail_redirects' % - zone.id) - mail_redirects = self._to_mail_redirects(response.object, zone) - return mail_redirects - - def ex_create_redirect(self, redirect_to, name, type, zone, iframe=None, - query=None): - """ - :param redirect_to: The data field. (redirect_to) - :type redirect_to: ``str`` - - :param name: The FQDN for the record. - :type name: ``str`` - - :param type: The type of redirects 301, 302 or 0 for iframes. - :type type: ``str`` - - :param zone: Zone to list redirects for. - :type zone: :class:`Zone` - - :param iframe: Title of iframe (optional). - :type iframe: ``str`` - - :param query: boolean Information about including query string when - redirecting. (optional). - :type query: ``bool`` - - :rtype: :class:`Record` - """ - r_json = {'name': name, 'redirect_to': redirect_to} - if type is not None: - r_json['redirect_type'] = type - if iframe is not None: - r_json['iframe_title'] = iframe - if query is not None: - r_json['redirect_query_string'] = query - r_data = json.dumps({'zone_redirect': r_json}) - try: - response = self.connection.request('/zones/%s/redirects' % zone.id, - method='POST', data=r_data) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - redirect = self._to_redirect(response.object, zone=zone) - return redirect - - def ex_create_mail_redirect(self, destination, source, zone): - """ - :param destination: The destination address of mail redirect. - :type destination: ``str`` - - :param source: The source address of mail redirect. - :type source: ``str`` - - :param zone: Zone to list redirects for. - :type zone: :class:`Zone` - - :rtype: ``list`` of :class:`MailRedirect` - """ - r_json = {'destination_address': destination, 'source_address': source} - r_data = json.dumps({'zone_mail_redirect': r_json}) - try: - response = self.connection.request('/zones/%s/mail_redirects' % - zone.id, method='POST', - data=r_data) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - mail_redirect = self._to_mail_redirect(response.object, zone=zone) - return mail_redirect - - def ex_get_redirect(self, zone_id, redirect_id): - """ - :param zone: Zone to list redirects for. - :type zone: :class:`Zone` - - :param redirect_id: Redirect id. - :type redirect_id: ``str`` - - :rtype: ``list`` of :class:`Redirect` - """ - try: - response = self.connection.request('/zones/%s/redirects/%s' % - (zone_id, redirect_id)) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise PointDNSException(value='Couldn\'t found redirect', - http_code=httplib.NOT_FOUND, - driver=self) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - redirect = self._to_redirect(response.object, zone_id=zone_id) - return redirect - - def ex_get_mail_redirects(self, zone_id, mail_r_id): - """ - :param zone: Zone to list redirects for. - :type zone: :class:`Zone` - - :param mail_r_id: Mail redirect id. - :type mail_r_id: ``str`` - - :rtype: ``list`` of :class:`MailRedirect` - """ - try: - response = self.connection.request('/zones/%s/mail_redirects/%s' % - (zone_id, mail_r_id)) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise PointDNSException(value='Couldn\'t found mail redirect', - http_code=httplib.NOT_FOUND, - driver=self) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - mail_redirect = self._to_mail_redirect(response.object, - zone_id=zone_id) - return mail_redirect - - def ex_update_redirect(self, redirect, redirect_to=None, name=None, - type=None, iframe=None, query=None): - """ - :param redirect: Record to update - :type id: :class:`Redirect` - - :param redirect_to: The data field. (optional). - :type redirect_to: ``str`` - - :param name: The FQDN for the record. - :type name: ``str`` - - :param type: The type of redirects 301, 302 or 0 for iframes. - (optional). - :type type: ``str`` - - :param iframe: Title of iframe (optional). - :type iframe: ``str`` - - :param query: boolean Information about including query string when - redirecting. (optional). - :type query: ``bool`` - - :rtype: ``list`` of :class:`Redirect` - """ - zone_id = redirect.zone.id - r_json = {} - if redirect_to is not None: - r_json['redirect_to'] = redirect_to - if name is not None: - r_json['name'] = name - if type is not None: - r_json['record_type'] = type - if iframe is not None: - r_json['iframe_title'] = iframe - if query is not None: - r_json['redirect_query_string'] = query - r_data = json.dumps({'zone_redirect': r_json}) - try: - response = self.connection.request('/zones/%s/redirects/%s' % - (zone_id, redirect.id), - method='PUT', data=r_data) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise PointDNSException(value='Couldn\'t found redirect', - http_code=httplib.NOT_FOUND, - driver=self) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - redirect = self._to_redirect(response.object, zone=redirect.zone) - return redirect - - def ex_update_mail_redirect(self, mail_r, destination, source=None): - """ - :param mail_r: Mail redirect to update - :type mail_r: :class:`MailRedirect` - - :param destination: The destination address of mail redirect. - :type destination: ``str`` - - :param source: The source address of mail redirect. (optional) - :type source: ``str`` - - :rtype: ``list`` of :class:`MailRedirect` - """ - zone_id = mail_r.zone.id - r_json = {'destination_address': destination} - if source is not None: - r_json['source_address'] = source - r_data = json.dumps({'zone_redirect': r_json}) - try: - response = self.connection.request('/zones/%s/mail_redirects/%s' % - (zone_id, mail_r.id), - method='PUT', data=r_data) - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise PointDNSException(value='Couldn\'t found mail redirect', - http_code=httplib.NOT_FOUND, - driver=self) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - mail_redirect = self._to_mail_redirect(response.object, - zone=mail_r.zone) - return mail_redirect - - def ex_delete_redirect(self, redirect): - """ - :param mail_r: Redirect to delete - :type mail_r: :class:`Redirect` - - :rtype: ``bool`` - """ - zone_id = redirect.zone.id - redirect_id = redirect.id - try: - self.connection.request('/zones/%s/redirects/%s' % (zone_id, - redirect_id), method='DELETE') - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise PointDNSException(value='Couldn\'t found redirect', - http_code=httplib.NOT_FOUND, - driver=self) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - return True - - def ex_delete_mail_redirect(self, mail_r): - """ - :param mail_r: Mail redirect to update - :type mail_r: :class:`MailRedirect` - - :rtype: ``bool`` - """ - zone_id = mail_r.zone.id - mail_r_id = mail_r.id - try: - self.connection.request('/zones/%s/mail_redirects/%s' % (zone_id, - mail_r_id), method='DELETE') - except (BaseHTTPError, MalformedResponseError): - e = sys.exc_info()[1] - if isinstance(e, MalformedResponseError) and e.body == 'Not found': - raise PointDNSException(value='Couldn\'t found mail redirect', - http_code=httplib.NOT_FOUND, - driver=self) - raise PointDNSException(value=e.message, http_code=e.code, - driver=self) - return True - - def _to_zones(self, data): - zones = [] - for zone in data: - _zone = self._to_zone(zone) - zones.append(_zone) - - return zones - - def _to_zone(self, data): - zone = data.get('zone') - id = zone.get('id') - name = zone.get('name') - ttl = zone.get('ttl') - extra = {'group': zone.get('group'), - 'user-id': zone.get('user-id')} - - # All zones are a primary ones by design, so they - # assume that are the master source of info about the - # zone, which is the case when domain DNS records - # points to PointDNS nameservers. - type = 'master' - - return Zone(id=id, domain=name, type=type, ttl=ttl, driver=self, - extra=extra) - - def _to_records(self, data, zone): - records = [] - for item in data: - record = self._to_record(item, zone=zone) - records.append(record) - return records - - def _to_record(self, data, zone_id=None, zone=None): - if not zone: # We need zone_id or zone - zone = self.get_zone(zone_id) - record = data.get('zone_record') - id = record.get('id') - name = record.get('name') - type = record.get('record_type') - data = record.get('data') - extra = {'ttl': record.get('ttl'), - 'zone_id': record.get('zone_id'), - 'aux': record.get('aux')} - return Record(id=id, name=name, type=type, data=data, zone=zone, - driver=self, ttl=record.get('ttl', None), extra=extra) - - def _to_redirects(self, data, zone): - redirects = [] - for item in data: - redirect = self._to_redirect(item, zone=zone) - redirects.append(redirect) - return redirects - - def _to_redirect(self, data, zone_id=None, zone=None): - if not zone: # We need zone_id or zone - zone = self.get_zone(zone_id) - record = data.get('zone_redirect') - id = record.get('id') - name = record.get('name') - redirect_to = record.get('redirect_to') - type = record.get('redirect_type') - iframe = record.get('iframe_title') - query = record.get('redirect_query_string') - return Redirect(id, name, redirect_to, type, self, zone, - iframe=iframe, query=query) - - def _to_mail_redirects(self, data, zone): - mail_redirects = [] - for item in data: - mail_redirect = self._to_mail_redirect(item, zone=zone) - mail_redirects.append(mail_redirect) - return mail_redirects - - def _to_mail_redirect(self, data, zone_id=None, zone=None): - if not zone: # We need zone_id or zone - zone = self.get_zone(zone_id) - record = data.get('zone_mail_redirect') - id = record.get('id') - destination = record.get('destination_address') - source = record.get('source_address') - return MailRedirect(id, source, destination, zone, self) http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/rackspace.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/rackspace.py b/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/rackspace.py deleted file mode 100644 index 6d231e0..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/rackspace.py +++ /dev/null @@ -1,662 +0,0 @@ -# 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 -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import copy - -from libcloud.utils.py3 import httplib -from libcloud.common.openstack import OpenStackDriverMixin -from libcloud.common.base import PollingConnection -from libcloud.common.exceptions import BaseHTTPError -from libcloud.common.types import LibcloudError -from libcloud.utils.misc import merge_valid_keys, get_new_obj -from libcloud.common.rackspace import AUTH_URL -from libcloud.compute.drivers.openstack import OpenStack_1_1_Connection -from libcloud.compute.drivers.openstack import OpenStack_1_1_Response - -from libcloud.dns.types import Provider, RecordType -from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError -from libcloud.dns.base import DNSDriver, Zone, Record - -__all__ = [ - 'RackspaceDNSResponse', - 'RackspaceDNSConnection' -] - -VALID_ZONE_EXTRA_PARAMS = ['email', 'comment', 'ns1'] -VALID_RECORD_EXTRA_PARAMS = ['ttl', 'comment', 'priority', 'created', - 'updated'] - - -class RackspaceDNSResponse(OpenStack_1_1_Response): - """ - Rackspace DNS Response class. - """ - - def parse_error(self): - status = int(self.status) - context = self.connection.context - body = self.parse_body() - - if status == httplib.NOT_FOUND: - if context['resource'] == 'zone': - raise ZoneDoesNotExistError(value='', driver=self, - zone_id=context['id']) - elif context['resource'] == 'record': - raise RecordDoesNotExistError(value='', driver=self, - record_id=context['id']) - if body: - if 'code' and 'message' in body: - err = '%s - %s (%s)' % (body['code'], body['message'], - body['details']) - return err - elif 'validationErrors' in body: - errors = [m for m in body['validationErrors']['messages']] - err = 'Validation errors: %s' % ', '.join(errors) - return err - - raise LibcloudError('Unexpected status code: %s' % (status)) - - -class RackspaceDNSConnection(OpenStack_1_1_Connection, PollingConnection): - """ - Rackspace DNS Connection class. - """ - - responseCls = RackspaceDNSResponse - XML_NAMESPACE = None - poll_interval = 2.5 - timeout = 30 - - auth_url = AUTH_URL - _auth_version = '2.0' - - def __init__(self, *args, **kwargs): - self.region = kwargs.pop('region', None) - super(RackspaceDNSConnection, self).__init__(*args, **kwargs) - - def get_poll_request_kwargs(self, response, context, request_kwargs): - job_id = response.object['jobId'] - kwargs = {'action': '/status/%s' % (job_id), - 'params': {'showDetails': True}} - return kwargs - - def has_completed(self, response): - status = response.object['status'] - if status == 'ERROR': - data = response.object['error'] - - if 'code' and 'message' in data: - message = '%s - %s (%s)' % (data['code'], data['message'], - data['details']) - else: - message = data['message'] - - raise LibcloudError(message, - driver=self.driver) - - return status == 'COMPLETED' - - def get_endpoint(self): - if '2.0' in self._auth_version: - ep = self.service_catalog.get_endpoint(name='cloudDNS', - service_type='rax:dns', - region=None) - else: - raise LibcloudError("Auth version %s not supported" % - (self._auth_version)) - - public_url = ep.url - - # This is a nasty hack, but because of how global auth and old accounts - # work, there is no way around it. - if self.region == 'us': - # Old UK account, which only has us endpoint in the catalog - public_url = public_url.replace('https://lon.dns.api', - 'https://dns.api') - if self.region == 'uk': - # Old US account, which only has uk endpoint in the catalog - public_url = public_url.replace('https://dns.api', - 'https://lon.dns.api') - - return public_url - - -class RackspacePTRRecord(object): - def __init__(self, id, ip, domain, driver, extra=None): - self.id = str(id) if id else None - self.ip = ip - self.type = RecordType.PTR - self.domain = domain - self.driver = driver - self.extra = extra or {} - - def update(self, domain, extra=None): - return self.driver.ex_update_ptr_record(record=self, domain=domain, - extra=extra) - - def delete(self): - return self.driver.ex_delete_ptr_record(record=self) - - def __repr__(self): - return ('<%s: ip=%s, domain=%s, provider=%s ...>' % - (self.__class__.__name__, self.ip, - self.domain, self.driver.name)) - - -class RackspaceDNSDriver(DNSDriver, OpenStackDriverMixin): - name = 'Rackspace DNS' - website = 'http://www.rackspace.com/' - type = Provider.RACKSPACE - connectionCls = RackspaceDNSConnection - - def __init__(self, key, secret=None, secure=True, host=None, port=None, - region='us', **kwargs): - valid_regions = self.list_regions() - if region not in valid_regions: - raise ValueError('Invalid region: %s' % (region)) - - OpenStackDriverMixin.__init__(self, **kwargs) - super(RackspaceDNSDriver, self).__init__(key=key, secret=secret, - host=host, port=port, - region=region) - - RECORD_TYPE_MAP = { - RecordType.A: 'A', - RecordType.AAAA: 'AAAA', - RecordType.CNAME: 'CNAME', - RecordType.MX: 'MX', - RecordType.NS: 'NS', - RecordType.PTR: 'PTR', - RecordType.SRV: 'SRV', - RecordType.TXT: 'TXT', - } - - @classmethod - def list_regions(cls): - return ['us', 'uk'] - - def iterate_zones(self): - offset = 0 - limit = 100 - while True: - params = { - 'limit': limit, - 'offset': offset, - } - response = self.connection.request( - action='/domains', params=params).object - zones_list = response['domains'] - for item in zones_list: - yield self._to_zone(item) - - if _rackspace_result_has_more(response, len(zones_list), limit): - offset += limit - else: - break - - def iterate_records(self, zone): - self.connection.set_context({'resource': 'zone', 'id': zone.id}) - offset = 0 - limit = 100 - while True: - params = { - 'showRecord': True, - 'limit': limit, - 'offset': offset, - } - response = self.connection.request( - action='/domains/%s' % (zone.id), params=params).object - records_list = response['recordsList'] - records = records_list['records'] - for item in records: - record = self._to_record(data=item, zone=zone) - yield record - - if _rackspace_result_has_more(records_list, len(records), limit): - offset += limit - else: - break - - def get_zone(self, zone_id): - self.connection.set_context({'resource': 'zone', 'id': zone_id}) - response = self.connection.request(action='/domains/%s' % (zone_id)) - zone = self._to_zone(data=response.object) - return zone - - def get_record(self, zone_id, record_id): - zone = self.get_zone(zone_id=zone_id) - self.connection.set_context({'resource': 'record', 'id': record_id}) - response = self.connection.request(action='/domains/%s/records/%s' % - (zone_id, record_id)).object - record = self._to_record(data=response, zone=zone) - return record - - def create_zone(self, domain, type='master', ttl=None, extra=None): - extra = extra if extra else {} - - # Email address is required - if 'email' not in extra: - raise ValueError('"email" key must be present in extra dictionary') - - payload = {'name': domain, 'emailAddress': extra['email'], - 'recordsList': {'records': []}} - - if ttl: - payload['ttl'] = ttl - - if 'comment' in extra: - payload['comment'] = extra['comment'] - - data = {'domains': [payload]} - response = self.connection.async_request(action='/domains', - method='POST', data=data) - zone = self._to_zone(data=response.object['response']['domains'][0]) - return zone - - def update_zone(self, zone, domain=None, type=None, ttl=None, extra=None): - # Only ttl, comment and email address can be changed - extra = extra if extra else {} - - if domain: - raise LibcloudError('Domain cannot be changed', driver=self) - - data = {} - - if ttl: - data['ttl'] = int(ttl) - - if 'email' in extra: - data['emailAddress'] = extra['email'] - - if 'comment' in extra: - data['comment'] = extra['comment'] - - type = type if type else zone.type - ttl = ttl if ttl else zone.ttl - - self.connection.set_context({'resource': 'zone', 'id': zone.id}) - self.connection.async_request(action='/domains/%s' % (zone.id), - method='PUT', data=data) - merged = merge_valid_keys(params=copy.deepcopy(zone.extra), - valid_keys=VALID_ZONE_EXTRA_PARAMS, - extra=extra) - updated_zone = get_new_obj(obj=zone, klass=Zone, - attributes={'type': type, - 'ttl': ttl, - 'extra': merged}) - return updated_zone - - def create_record(self, name, zone, type, data, extra=None): - # Name must be a FQDN - e.g. if domain is "foo.com" then a record - # name is "bar.foo.com" - extra = extra if extra else {} - - name = self._to_full_record_name(domain=zone.domain, name=name) - data = {'name': name, 'type': self.RECORD_TYPE_MAP[type], - 'data': data} - - if 'ttl' in extra: - data['ttl'] = int(extra['ttl']) - - if 'priority' in extra: - data['priority'] = int(extra['priority']) - - payload = {'records': [data]} - self.connection.set_context({'resource': 'zone', 'id': zone.id}) - response = self.connection.async_request(action='/domains/%s/records' - % (zone.id), data=payload, - method='POST').object - record = self._to_record(data=response['response']['records'][0], - zone=zone) - return record - - def update_record(self, record, name=None, type=None, data=None, - extra=None): - # Only data, ttl, and comment attributes can be modified, but name - # attribute must always be present. - extra = extra if extra else {} - - name = self._to_full_record_name(domain=record.zone.domain, - name=record.name) - payload = {'name': name} - - if data: - payload['data'] = data - - if 'ttl' in extra: - payload['ttl'] = extra['ttl'] - - if 'comment' in extra: - payload['comment'] = extra['comment'] - - type = type if type is not None else record.type - data = data if data else record.data - - self.connection.set_context({'resource': 'record', 'id': record.id}) - self.connection.async_request(action='/domains/%s/records/%s' % - (record.zone.id, record.id), - method='PUT', data=payload) - - merged = merge_valid_keys(params=copy.deepcopy(record.extra), - valid_keys=VALID_RECORD_EXTRA_PARAMS, - extra=extra) - updated_record = get_new_obj(obj=record, klass=Record, - attributes={'type': type, - 'data': data, - 'driver': self, - 'extra': merged}) - return updated_record - - def delete_zone(self, zone): - self.connection.set_context({'resource': 'zone', 'id': zone.id}) - self.connection.async_request(action='/domains/%s' % (zone.id), - method='DELETE') - return True - - def delete_record(self, record): - self.connection.set_context({'resource': 'record', 'id': record.id}) - self.connection.async_request(action='/domains/%s/records/%s' % - (record.zone.id, record.id), - method='DELETE') - return True - - def ex_iterate_ptr_records(self, device): - """ - Return a generator to iterate over existing PTR Records. - - The ``device`` should be an instance of one of these: - :class:`libcloud.compute.base.Node` - :class:`libcloud.loadbalancer.base.LoadBalancer` - - And it needs to have the following ``extra`` fields set: - service_name - the service catalog name for the device - uri - the URI pointing to the GET endpoint for the device - - Those are automatically set for you if you got the device from - the Rackspace driver for that service. - - For example: - server = rs_compute.ex_get_node_details(id) - ptr_iter = rs_dns.ex_list_ptr_records(server) - - loadbalancer = rs_lbs.get_balancer(id) - ptr_iter = rs_dns.ex_list_ptr_records(loadbalancer) - - Note: the Rackspace DNS API docs indicate that the device 'href' is - optional, but testing does not bear this out. It throws a - 400 Bad Request error if you do not pass in the 'href' from - the server or loadbalancer. So ``device`` is required. - - :param device: the device that owns the IP - :rtype: ``generator`` of :class:`RackspacePTRRecord` - """ - _check_ptr_extra_fields(device) - params = {'href': device.extra['uri']} - - service_name = device.extra['service_name'] - - # without a valid context, the 404 on empty list will blow up - # in the error-handling code - self.connection.set_context({'resource': 'ptr_records'}) - try: - response = self.connection.request( - action='/rdns/%s' % (service_name), params=params).object - records = response['records'] - link = dict(rel=service_name, **params) - for item in records: - record = self._to_ptr_record(data=item, link=link) - yield record - except BaseHTTPError as exc: - # 404 just means empty list - if exc.code == 404: - return - raise - - def ex_get_ptr_record(self, service_name, record_id): - """ - Get a specific PTR record by id. - - :param service_name: the service catalog name of the linked device(s) - i.e. cloudLoadBalancers or cloudServersOpenStack - :param record_id: the id (i.e. PTR-12345) of the PTR record - :rtype: instance of :class:`RackspacePTRRecord` - """ - self.connection.set_context({'resource': 'record', 'id': record_id}) - response = self.connection.request( - action='/rdns/%s/%s' % (service_name, record_id)).object - item = next(iter(response['recordsList']['records'])) - return self._to_ptr_record(data=item, link=response['link']) - - def ex_create_ptr_record(self, device, ip, domain, extra=None): - """ - Create a PTR record for a specific IP on a specific device. - - The ``device`` should be an instance of one of these: - :class:`libcloud.compute.base.Node` - :class:`libcloud.loadbalancer.base.LoadBalancer` - - And it needs to have the following ``extra`` fields set: - service_name - the service catalog name for the device - uri - the URI pointing to the GET endpoint for the device - - Those are automatically set for you if you got the device from - the Rackspace driver for that service. - - For example: - server = rs_compute.ex_get_node_details(id) - rs_dns.create_ptr_record(server, ip, domain) - - loadbalancer = rs_lbs.get_balancer(id) - rs_dns.create_ptr_record(loadbalancer, ip, domain) - - :param device: the device that owns the IP - :param ip: the IP for which you want to set reverse DNS - :param domain: the fqdn you want that IP to represent - :param extra: a ``dict`` with optional extra values: - ttl - the time-to-live of the PTR record - :rtype: instance of :class:`RackspacePTRRecord` - """ - _check_ptr_extra_fields(device) - - if extra is None: - extra = {} - - # the RDNS API reverse the name and data fields for PTRs - # the record name *should* be the ip and the data the fqdn - data = { - "name": domain, - "type": RecordType.PTR, - "data": ip - } - - if 'ttl' in extra: - data['ttl'] = extra['ttl'] - - payload = { - "recordsList": { - "records": [data] - }, - "link": { - "content": "", - "href": device.extra['uri'], - "rel": device.extra['service_name'], - } - } - response = self.connection.async_request( - action='/rdns', method='POST', data=payload).object - item = next(iter(response['response']['records'])) - return self._to_ptr_record(data=item, link=payload['link']) - - def ex_update_ptr_record(self, record, domain=None, extra=None): - """ - Update a PTR record for a specific IP on a specific device. - - If you need to change the domain or ttl, use this API to - update the record by deleting the old one and creating a new one. - - :param record: the original :class:`RackspacePTRRecord` - :param domain: the fqdn you want that IP to represent - :param extra: a ``dict`` with optional extra values: - ttl - the time-to-live of the PTR record - :rtype: instance of :class:`RackspacePTRRecord` - """ - if domain is not None and domain == record.domain: - domain = None - - if extra is not None: - extra = dict(extra) - for key in extra: - if key in record.extra and record.extra[key] == extra[key]: - del extra[key] - - if domain is None and not extra: - # nothing to do, it already matches - return record - - _check_ptr_extra_fields(record) - ip = record.ip - - self.ex_delete_ptr_record(record) - # records have the same metadata in 'extra' as the original device - # so you can pass the original record object in instead - return self.ex_create_ptr_record(record, ip, domain, extra=extra) - - def ex_delete_ptr_record(self, record): - """ - Delete an existing PTR Record - - :param record: the original :class:`RackspacePTRRecord` - :rtype: ``bool`` - """ - _check_ptr_extra_fields(record) - self.connection.set_context({'resource': 'record', 'id': record.id}) - self.connection.async_request( - action='/rdns/%s' % (record.extra['service_name']), - method='DELETE', - params={'href': record.extra['uri'], 'ip': record.ip}, - ) - return True - - def _to_zone(self, data): - id = data['id'] - domain = data['name'] - type = 'master' - ttl = data.get('ttl', 0) - extra = {} - - if 'emailAddress' in data: - extra['email'] = data['emailAddress'] - - if 'comment' in data: - extra['comment'] = data['comment'] - - zone = Zone(id=str(id), domain=domain, type=type, ttl=int(ttl), - driver=self, extra=extra) - return zone - - def _to_record(self, data, zone): - id = data['id'] - fqdn = data['name'] - name = self._to_partial_record_name(domain=zone.domain, name=fqdn) - type = self._string_to_record_type(data['type']) - record_data = data['data'] - extra = {'fqdn': fqdn} - - for key in VALID_RECORD_EXTRA_PARAMS: - if key in data: - extra[key] = data[key] - - record = Record(id=str(id), name=name, type=type, data=record_data, - zone=zone, driver=self, ttl=extra.get('ttl', None), - extra=extra) - return record - - def _to_ptr_record(self, data, link): - id = data['id'] - ip = data['data'] - domain = data['name'] - extra = {'uri': link['href'], 'service_name': link['rel']} - - for key in VALID_RECORD_EXTRA_PARAMS: - if key in data: - extra[key] = data[key] - - record = RackspacePTRRecord(id=str(id), ip=ip, domain=domain, - driver=self, extra=extra) - return record - - def _to_full_record_name(self, domain, name): - """ - Build a FQDN from a domain and record name. - - :param domain: Domain name. - :type domain: ``str`` - - :param name: Record name. - :type name: ``str`` - """ - if name: - name = '%s.%s' % (name, domain) - else: - name = domain - - return name - - def _to_partial_record_name(self, domain, name): - """ - Remove domain portion from the record name. - - :param domain: Domain name. - :type domain: ``str`` - - :param name: Full record name (fqdn). - :type name: ``str`` - """ - if name == domain: - # Map "root" record names to None to be consistent with other - # drivers - return None - - # Strip domain portion - name = name.replace('.%s' % (domain), '') - return name - - def _ex_connection_class_kwargs(self): - kwargs = self.openstack_connection_kwargs() - kwargs['region'] = self.region - return kwargs - - -def _rackspace_result_has_more(response, result_length, limit): - # If rackspace returns less than the limit, then we've reached the end of - # the result set. - if result_length < limit: - return False - - # Paginated results return links to the previous and next sets of data, but - # 'next' only exists when there is more to get. - for item in response.get('links', ()): - if item['rel'] == 'next': - return True - return False - - -def _check_ptr_extra_fields(device_or_record): - if not (hasattr(device_or_record, 'extra') and - isinstance(device_or_record.extra, dict) and - device_or_record.extra.get('uri') is not None and - device_or_record.extra.get('service_name') is not None): - raise LibcloudError("Can't create PTR Record for %s because it " - "doesn't have a 'uri' and 'service_name' in " - "'extra'" % device_or_record) http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/route53.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/route53.py b/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/route53.py deleted file mode 100644 index 040cdbc..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/dns/drivers/route53.py +++ /dev/null @@ -1,548 +0,0 @@ -# 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 -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__all__ = [ - 'Route53DNSDriver' -] - -import base64 -import hmac -import datetime -import uuid -import copy -from libcloud.utils.py3 import httplib - -from hashlib import sha1 - -try: - from lxml import etree as ET -except ImportError: - from xml.etree import ElementTree as ET - -from libcloud.utils.py3 import b, urlencode - -from libcloud.utils.xml import findtext, findall, fixxpath -from libcloud.dns.types import Provider, RecordType -from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError -from libcloud.dns.base import DNSDriver, Zone, Record -from libcloud.common.types import LibcloudError -from libcloud.common.aws import AWSGenericResponse -from libcloud.common.base import ConnectionUserAndKey - - -API_VERSION = '2012-02-29' -API_HOST = 'route53.amazonaws.com' -API_ROOT = '/%s/' % (API_VERSION) - -NAMESPACE = 'https://%s/doc%s' % (API_HOST, API_ROOT) - - -class InvalidChangeBatch(LibcloudError): - pass - - -class Route53DNSResponse(AWSGenericResponse): - """ - Amazon Route53 response class. - """ - - namespace = NAMESPACE - xpath = 'Error' - - exceptions = { - 'NoSuchHostedZone': ZoneDoesNotExistError, - 'InvalidChangeBatch': InvalidChangeBatch, - } - - -class Route53Connection(ConnectionUserAndKey): - host = API_HOST - responseCls = Route53DNSResponse - - def pre_connect_hook(self, params, headers): - time_string = datetime.datetime.utcnow() \ - .strftime('%a, %d %b %Y %H:%M:%S GMT') - headers['Date'] = time_string - tmp = [] - - signature = self._get_aws_auth_b64(self.key, time_string) - auth = {'AWSAccessKeyId': self.user_id, 'Signature': signature, - 'Algorithm': 'HmacSHA1'} - - for k, v in auth.items(): - tmp.append('%s=%s' % (k, v)) - - headers['X-Amzn-Authorization'] = 'AWS3-HTTPS ' + ','.join(tmp) - - return params, headers - - def _get_aws_auth_b64(self, secret_key, time_string): - b64_hmac = base64.b64encode( - hmac.new(b(secret_key), b(time_string), digestmod=sha1).digest() - ) - - return b64_hmac.decode('utf-8') - - -class Route53DNSDriver(DNSDriver): - type = Provider.ROUTE53 - name = 'Route53 DNS' - website = 'http://aws.amazon.com/route53/' - connectionCls = Route53Connection - - RECORD_TYPE_MAP = { - RecordType.A: 'A', - RecordType.AAAA: 'AAAA', - RecordType.CNAME: 'CNAME', - RecordType.MX: 'MX', - RecordType.NS: 'NS', - RecordType.PTR: 'PTR', - RecordType.SOA: 'SOA', - RecordType.SPF: 'SPF', - RecordType.SRV: 'SRV', - RecordType.TXT: 'TXT', - } - - def iterate_zones(self): - return self._get_more('zones') - - def iterate_records(self, zone): - return self._get_more('records', zone=zone) - - def get_zone(self, zone_id): - self.connection.set_context({'zone_id': zone_id}) - uri = API_ROOT + 'hostedzone/' + zone_id - data = self.connection.request(uri).object - elem = findall(element=data, xpath='HostedZone', - namespace=NAMESPACE)[0] - return self._to_zone(elem) - - def get_record(self, zone_id, record_id): - zone = self.get_zone(zone_id=zone_id) - record_type, name = record_id.split(':', 1) - if name: - full_name = ".".join((name, zone.domain)) - else: - full_name = zone.domain - self.connection.set_context({'zone_id': zone_id}) - params = urlencode({ - 'name': full_name, - 'type': record_type, - 'maxitems': '1' - }) - uri = API_ROOT + 'hostedzone/' + zone_id + '/rrset?' + params - data = self.connection.request(uri).object - - record = self._to_records(data=data, zone=zone)[0] - - # A cute aspect of the /rrset filters is that they are more pagination - # hints than filters!! - # So will return a result even if its not what you asked for. - record_type_num = self._string_to_record_type(record_type) - if record.name != name or record.type != record_type_num: - raise RecordDoesNotExistError(value='', driver=self, - record_id=record_id) - - return record - - def create_zone(self, domain, type='master', ttl=None, extra=None): - zone = ET.Element('CreateHostedZoneRequest', {'xmlns': NAMESPACE}) - ET.SubElement(zone, 'Name').text = domain - ET.SubElement(zone, 'CallerReference').text = str(uuid.uuid4()) - - if extra and 'Comment' in extra: - hzg = ET.SubElement(zone, 'HostedZoneConfig') - ET.SubElement(hzg, 'Comment').text = extra['Comment'] - - uri = API_ROOT + 'hostedzone' - data = ET.tostring(zone) - rsp = self.connection.request(uri, method='POST', data=data).object - - elem = findall(element=rsp, xpath='HostedZone', namespace=NAMESPACE)[0] - return self._to_zone(elem=elem) - - def delete_zone(self, zone, ex_delete_records=False): - self.connection.set_context({'zone_id': zone.id}) - - if ex_delete_records: - self.ex_delete_all_records(zone=zone) - - uri = API_ROOT + 'hostedzone/%s' % (zone.id) - response = self.connection.request(uri, method='DELETE') - return response.status in [httplib.OK] - - def create_record(self, name, zone, type, data, extra=None): - extra = extra or {} - batch = [('CREATE', name, type, data, extra)] - self._post_changeset(zone, batch) - id = ':'.join((self.RECORD_TYPE_MAP[type], name)) - return Record(id=id, name=name, type=type, data=data, zone=zone, - driver=self, ttl=extra.get('ttl', None), extra=extra) - - def update_record(self, record, name=None, type=None, data=None, - extra=None): - name = name or record.name - type = type or record.type - extra = extra or record.extra - - if not extra: - extra = record.extra - - # Multiple value records need to be handled specially - we need to - # pass values for other records as well - multiple_value_record = record.extra.get('_multi_value', False) - other_records = record.extra.get('_other_records', []) - - if multiple_value_record and other_records: - self._update_multi_value_record(record=record, name=name, - type=type, data=data, - extra=extra) - else: - self._update_single_value_record(record=record, name=name, - type=type, data=data, - extra=extra) - - id = ':'.join((self.RECORD_TYPE_MAP[type], name)) - return Record(id=id, name=name, type=type, data=data, zone=record.zone, - driver=self, ttl=extra.get('ttl', None), extra=extra) - - def delete_record(self, record): - try: - r = record - batch = [('DELETE', r.name, r.type, r.data, r.extra)] - self._post_changeset(record.zone, batch) - except InvalidChangeBatch: - raise RecordDoesNotExistError(value='', driver=self, - record_id=r.id) - return True - - def ex_create_multi_value_record(self, name, zone, type, data, extra=None): - """ - Create a record with multiple values with a single call. - - :return: A list of created records. - :rtype: ``list`` of :class:`libcloud.dns.base.Record` - """ - extra = extra or {} - - attrs = {'xmlns': NAMESPACE} - changeset = ET.Element('ChangeResourceRecordSetsRequest', attrs) - batch = ET.SubElement(changeset, 'ChangeBatch') - changes = ET.SubElement(batch, 'Changes') - - change = ET.SubElement(changes, 'Change') - ET.SubElement(change, 'Action').text = 'CREATE' - - rrs = ET.SubElement(change, 'ResourceRecordSet') - ET.SubElement(rrs, 'Name').text = name + '.' + zone.domain - ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[type] - ET.SubElement(rrs, 'TTL').text = str(extra.get('ttl', '0')) - - rrecs = ET.SubElement(rrs, 'ResourceRecords') - - # Value is provided as a multi line string - values = [value.strip() for value in data.split('\n') if - value.strip()] - - for value in values: - rrec = ET.SubElement(rrecs, 'ResourceRecord') - ET.SubElement(rrec, 'Value').text = value - - uri = API_ROOT + 'hostedzone/' + zone.id + '/rrset' - data = ET.tostring(changeset) - self.connection.set_context({'zone_id': zone.id}) - self.connection.request(uri, method='POST', data=data) - - id = ':'.join((self.RECORD_TYPE_MAP[type], name)) - - records = [] - for value in values: - record = Record(id=id, name=name, type=type, data=value, zone=zone, - driver=self, ttl=extra.get('ttl', None), - extra=extra) - records.append(record) - - return records - - def ex_delete_all_records(self, zone): - """ - Remove all the records for the provided zone. - - :param zone: Zone to delete records for. - :type zone: :class:`Zone` - """ - deletions = [] - for r in zone.list_records(): - if r.type in (RecordType.NS, RecordType.SOA): - continue - deletions.append(('DELETE', r.name, r.type, r.data, r.extra)) - - if deletions: - self._post_changeset(zone, deletions) - - def _update_single_value_record(self, record, name=None, type=None, - data=None, extra=None): - batch = [ - ('DELETE', record.name, record.type, record.data, record.extra), - ('CREATE', name, type, data, extra) - ] - - return self._post_changeset(record.zone, batch) - - def _update_multi_value_record(self, record, name=None, type=None, - data=None, extra=None): - other_records = record.extra.get('_other_records', []) - - attrs = {'xmlns': NAMESPACE} - changeset = ET.Element('ChangeResourceRecordSetsRequest', attrs) - batch = ET.SubElement(changeset, 'ChangeBatch') - changes = ET.SubElement(batch, 'Changes') - - # Delete existing records - change = ET.SubElement(changes, 'Change') - ET.SubElement(change, 'Action').text = 'DELETE' - - rrs = ET.SubElement(change, 'ResourceRecordSet') - - if record.name: - record_name = record.name + '.' + record.zone.domain - else: - record_name = record.zone.domain - - ET.SubElement(rrs, 'Name').text = record_name - ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[record.type] - ET.SubElement(rrs, 'TTL').text = str(record.extra.get('ttl', '0')) - - rrecs = ET.SubElement(rrs, 'ResourceRecords') - - rrec = ET.SubElement(rrecs, 'ResourceRecord') - ET.SubElement(rrec, 'Value').text = record.data - - for other_record in other_records: - rrec = ET.SubElement(rrecs, 'ResourceRecord') - ET.SubElement(rrec, 'Value').text = other_record['data'] - - # Re-create new (updated) records. Since we are updating a multi value - # record, only a single record is updated and others are left as is. - change = ET.SubElement(changes, 'Change') - ET.SubElement(change, 'Action').text = 'CREATE' - - rrs = ET.SubElement(change, 'ResourceRecordSet') - - if name: - record_name = name + '.' + record.zone.domain - else: - record_name = record.zone.domain - - ET.SubElement(rrs, 'Name').text = record_name - ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[type] - ET.SubElement(rrs, 'TTL').text = str(extra.get('ttl', '0')) - - rrecs = ET.SubElement(rrs, 'ResourceRecords') - - rrec = ET.SubElement(rrecs, 'ResourceRecord') - ET.SubElement(rrec, 'Value').text = data - - for other_record in other_records: - rrec = ET.SubElement(rrecs, 'ResourceRecord') - ET.SubElement(rrec, 'Value').text = other_record['data'] - uri = API_ROOT + 'hostedzone/' + record.zone.id + '/rrset' - data = ET.tostring(changeset) - self.connection.set_context({'zone_id': record.zone.id}) - response = self.connection.request(uri, method='POST', data=data) - - return response.status == httplib.OK - - def _post_changeset(self, zone, changes_list): - attrs = {'xmlns': NAMESPACE} - changeset = ET.Element('ChangeResourceRecordSetsRequest', attrs) - batch = ET.SubElement(changeset, 'ChangeBatch') - changes = ET.SubElement(batch, 'Changes') - - for action, name, type_, data, extra in changes_list: - change = ET.SubElement(changes, 'Change') - ET.SubElement(change, 'Action').text = action - - rrs = ET.SubElement(change, 'ResourceRecordSet') - - if name: - record_name = name + '.' + zone.domain - else: - record_name = zone.domain - - ET.SubElement(rrs, 'Name').text = record_name - ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[type_] - ET.SubElement(rrs, 'TTL').text = str(extra.get('ttl', '0')) - - rrecs = ET.SubElement(rrs, 'ResourceRecords') - rrec = ET.SubElement(rrecs, 'ResourceRecord') - if 'priority' in extra: - data = '%s %s' % (extra['priority'], data) - ET.SubElement(rrec, 'Value').text = data - - uri = API_ROOT + 'hostedzone/' + zone.id + '/rrset' - data = ET.tostring(changeset) - self.connection.set_context({'zone_id': zone.id}) - response = self.connection.request(uri, method='POST', data=data) - - return response.status == httplib.OK - - def _to_zones(self, data): - zones = [] - for element in data.findall(fixxpath(xpath='HostedZones/HostedZone', - namespace=NAMESPACE)): - zones.append(self._to_zone(element)) - - return zones - - def _to_zone(self, elem): - name = findtext(element=elem, xpath='Name', namespace=NAMESPACE) - id = findtext(element=elem, xpath='Id', - namespace=NAMESPACE).replace('/hostedzone/', '') - comment = findtext(element=elem, xpath='Config/Comment', - namespace=NAMESPACE) - resource_record_count = int(findtext(element=elem, - xpath='ResourceRecordSetCount', - namespace=NAMESPACE)) - - extra = {'Comment': comment, 'ResourceRecordSetCount': - resource_record_count} - - zone = Zone(id=id, domain=name, type='master', ttl=0, driver=self, - extra=extra) - return zone - - def _to_records(self, data, zone): - records = [] - elems = data.findall( - fixxpath(xpath='ResourceRecordSets/ResourceRecordSet', - namespace=NAMESPACE)) - for elem in elems: - record_set = elem.findall(fixxpath( - xpath='ResourceRecords/ResourceRecord', - namespace=NAMESPACE)) - record_count = len(record_set) - multiple_value_record = (record_count > 1) - - record_set_records = [] - - for index, record in enumerate(record_set): - # Need to special handling for records with multiple values for - # update to work correctly - record = self._to_record(elem=elem, zone=zone, index=index) - record.extra['_multi_value'] = multiple_value_record - - if multiple_value_record: - record.extra['_other_records'] = [] - - record_set_records.append(record) - - # Store reference to other records so update works correctly - if multiple_value_record: - for index in range(0, len(record_set_records)): - record = record_set_records[index] - - for other_index, other_record in \ - enumerate(record_set_records): - if index == other_index: - # Skip current record - continue - - extra = copy.deepcopy(other_record.extra) - extra.pop('_multi_value') - extra.pop('_other_records') - - item = {'name': other_record.name, - 'data': other_record.data, - 'type': other_record.type, - 'extra': extra} - record.extra['_other_records'].append(item) - - records.extend(record_set_records) - - return records - - def _to_record(self, elem, zone, index=0): - name = findtext(element=elem, xpath='Name', - namespace=NAMESPACE) - name = name[:-len(zone.domain) - 1] - - type = self._string_to_record_type(findtext(element=elem, xpath='Type', - namespace=NAMESPACE)) - ttl = findtext(element=elem, xpath='TTL', namespace=NAMESPACE) - if ttl is not None: - ttl = int(ttl) - - value_elem = elem.findall( - fixxpath(xpath='ResourceRecords/ResourceRecord', - namespace=NAMESPACE))[index] - data = findtext(element=(value_elem), xpath='Value', - namespace=NAMESPACE) - - extra = {'ttl': ttl} - - if type == 'MX': - split = data.split() - priority, data = split - extra['priority'] = int(priority) - elif type == 'SRV': - split = data.split() - priority, weight, port, data = split - extra['priority'] = int(priority) - extra['weight'] = int(weight) - extra['port'] = int(port) - - id = ':'.join((self.RECORD_TYPE_MAP[type], name)) - record = Record(id=id, name=name, type=type, data=data, zone=zone, - driver=self, ttl=extra.get('ttl', None), extra=extra) - return record - - def _get_more(self, rtype, **kwargs): - exhausted = False - last_key = None - while not exhausted: - items, last_key, exhausted = self._get_data(rtype, last_key, - **kwargs) - for item in items: - yield item - - def _get_data(self, rtype, last_key, **kwargs): - params = {} - if last_key: - params['name'] = last_key - path = API_ROOT + 'hostedzone' - - if rtype == 'zones': - response = self.connection.request(path, params=params) - transform_func = self._to_zones - elif rtype == 'records': - zone = kwargs['zone'] - path += '/%s/rrset' % (zone.id) - self.connection.set_context({'zone_id': zone.id}) - response = self.connection.request(path, params=params) - transform_func = self._to_records - - if response.status == httplib.OK: - is_truncated = findtext(element=response.object, - xpath='IsTruncated', - namespace=NAMESPACE) - exhausted = is_truncated != 'true' - last_key = findtext(element=response.object, - xpath='NextRecordName', - namespace=NAMESPACE) - items = transform_func(data=response.object, **kwargs) - return items, last_key, exhausted - else: - return [], None, True