libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject [01/21] libcloud git commit: DigitalOceanDNSDriver implementation.
Date Sun, 14 Jun 2015 10:52:12 GMT
Repository: libcloud
Updated Branches:
  refs/heads/trunk 78b95fd9d -> ac75a302d


DigitalOceanDNSDriver implementation.

Pulled connection/response from libcloud.compute.driver.digitalocean into libcloud.common.digitalocean
Implemented majority functions in libcloud.dns.driver.digitalocean

Signed-off-by: Tomaz Muraus <tomaz@tomaz.me>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/f50b8d42
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/f50b8d42
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/f50b8d42

Branch: refs/heads/trunk
Commit: f50b8d421f4b9b930abddda7b400111070c1f2f8
Parents: 78b95fd
Author: Javier Castillo II <jcastillo2nd@libcloud.castlecorporation.com>
Authored: Sat Apr 11 01:26:18 2015 +0000
Committer: Tomaz Muraus <tomaz@tomaz.me>
Committed: Sun Jun 14 18:05:57 2015 +0800

----------------------------------------------------------------------
 libcloud/common/digitalocean.py      | 120 ++++++++++++++
 libcloud/dns/drivers/digitalocean.py | 267 ++++++++++++++++++++++++++++++
 libcloud/dns/providers.py            |   2 +
 libcloud/dns/types.py                |   1 +
 4 files changed, 390 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/f50b8d42/libcloud/common/digitalocean.py
----------------------------------------------------------------------
diff --git a/libcloud/common/digitalocean.py b/libcloud/common/digitalocean.py
new file mode 100644
index 0000000..097b207
--- /dev/null
+++ b/libcloud/common/digitalocean.py
@@ -0,0 +1,120 @@
+# 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.
+
+"""
+Common settings and connection objects for DigitalOcean Cloud
+"""
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.base import ConnectionUserAndKey, ConnectionKey
+from libcloud.common.base import JsonResponse
+from libcloud.common.types import InvalidCredsError
+
+__all__ = [
+    'DigitalOcean_v1_Response',
+    'DigitalOcean_v1_Connection',
+    'DigitalOcean_v2_Response'
+    'DigitalOcean_v2_Connection',
+]
+
+AUTH_URL = 'https://api.digitalocean.com'
+
+
+class DigitalOcean_v1_Response(JsonResponse):
+    def parse_error(self):
+        if self.status == httplib.FOUND and '/api/error' in self.body:
+            # Hacky, but DigitalOcean error responses are awful
+            raise InvalidCredsError(self.body)
+        elif self.status == httplib.UNAUTHORIZED:
+            body = self.parse_body()
+            raise InvalidCredsError(body['message'])
+        else:
+            body = self.parse_body()
+
+            if 'error_message' in body:
+                error = '%s (code: %s)' % (body['error_message'], self.status)
+            else:
+                error = body
+            return error
+
+
+class DigitalOcean_v1_Connection(ConnectionUserAndKey):
+    """
+    Connection class for the DigitalOcean (v1) driver.
+    """
+
+    host = 'api.digitalocean.com'
+    responseCls = DigitalOcean_v1_Response
+
+    def add_default_params(self, params):
+        """
+        Add parameters that are necessary for every request
+
+        This method adds ``client_id`` and ``api_key`` to
+        the request.
+        """
+        params['client_id'] = self.user_id
+        params['api_key'] = self.key
+        return params
+
+
+class DigitalOcean_v2_Response(JsonResponse):
+    valid_response_codes = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
+                            httplib.NO_CONTENT]
+
+    def parse_error(self):
+        if self.status == httplib.UNAUTHORIZED:
+            body = self.parse_body()
+            raise InvalidCredsError(body['message'])
+        else:
+            body = self.parse_body()
+            if 'message' in body:
+                error = '%s (code: %s)' % (body['message'], self.status)
+            else:
+                error = body
+            return error
+
+    def success(self):
+        return self.status in self.valid_response_codes
+
+
+class DigitalOcean_v2_Connection(ConnectionKey):
+    """
+    Connection class for the DigitalOcean (v2) driver.
+    """
+
+    host = 'api.digitalocean.com'
+    responseCls = DigitalOcean_v2_Response
+
+    def add_default_headers(self, headers):
+        """
+        Add headers that are necessary for every request
+
+        This method adds ``token`` to the request.
+        """
+        headers['Authorization'] = 'Bearer %s' % (self.key)
+        headers['Content-Type'] = 'application/json'
+        return headers
+
+class DigitalOceanConnection(DigitalOcean_v2_Connection):
+    """
+    Connection class for the DigitalOcean driver.
+    """
+    pass
+
+
+class DigitalOceanResponse(DigitalOcean_v2_Response):
+    pass

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f50b8d42/libcloud/dns/drivers/digitalocean.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/drivers/digitalocean.py b/libcloud/dns/drivers/digitalocean.py
new file mode 100644
index 0000000..40357b9
--- /dev/null
+++ b/libcloud/dns/drivers/digitalocean.py
@@ -0,0 +1,267 @@
+# 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.
+"""
+Digital Ocean DNS Driver
+"""
+
+__all__ = [
+    'DigitalOceanDNSDriver'
+]
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.digitalocean import DigitalOceanConnection, DigitalOceanResponse
+from libcloud.dns.types import Provider, RecordType
+from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError
+from libcloud.dns.base import DNSDriver, Zone, Record
+
+
+class DigitalOceanDNSDriver(DNSDriver):
+    connectionCls = DigitalOceanConnection
+    type = Provider.DIGITAL_OCEAN
+    name = "DigitalOcean"
+    website = 'https://www.digitalocean.com'
+
+    RECORD_TYPE_MAP = {
+        RecordType.NS : 'NS',
+        RecordType.A : 'A',
+        RecordType.AAAA : 'AAAA',
+        RecordType.CNAME : 'CNAME',
+        RecordType.MX : 'MX',
+        RecordType.TXT : 'TXT',
+        RecordType.SRV : 'SRV',
+    }
+
+    def list_zones(self):
+        """
+        Return a list of zones.
+
+        :return: ``list`` of :class:`Zone`
+        """
+        data = self._paginated_request('/v2/domains', 'domains')
+        return list(map(self._to_zone, data))
+
+    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`
+        """
+        data = self._paginated_request('/v2/domains/%s/records' % (zone.id),
+                                       'domain_records')
+# TODO: Not use list comprehension to add zone to record for proper data map
+#       functionality?
+        return list(map(self._to_record, data, [zone for z in data]))
+
+    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`
+        """
+        data = self.connection.request('/v2/domains/%s' %
+               (zone_id)).object['domain']
+
+        return self._to_zone(data)
+
+    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`
+        """
+        data = self.connection.request('/v2/domains/%s/records/%s' % (zone_id,
+                                       record_id)).object['domain_record']
+
+# TODO: Any way of not using get_zone which polls the API again
+#       without breaking the DNSDriver.get_record parameters?
+        return self._to_record(data, self.get_zone(zone_id))
+
+    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 (master / slave) (does nothing).
+        :type  type: ``str``
+
+        :param ttl: TTL for new records. (does nothing)
+        :type  ttl: ``int``
+
+        :param extra: Extra attributes (to set ip). (optional)
+                      Note: This can be used to set the default A record with
+                      {"ip" : "IP.AD.DR.ESS"} otherwise 127.0.0.1 is used
+        :type extra: ``dict``
+
+        :rtype: :class:`Zone`
+        """
+        params = {'name' : domain}
+        try:
+            params['ip_address'] = extra['ip']
+        except:
+            params['ip_address'] = '127.0.0.1'
+
+        res = self.connection.request('/v2/domains', params=params,
+                                      method='POST')
+
+        return Zone(id=res.object['domain']['name'],
+                    domain=res.object['domain']['name'],
+                    type='master', ttl=1800, driver=self, extra={})
+
+    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 for MX and SRV. (Depends on record)
+                      {"priority" : 0, "port" : 443, "weight" : 100}
+        :type extra: ``dict``
+
+        :rtype: :class:`Record`
+        """
+        params = {
+            "type" : self.RECORD_TYPE_MAP[type],
+            "name" : name,
+            "data" : data
+        }
+        if extra:
+            try:
+                params['priority'] = extra['priority']
+            except KeyError:
+                params['priority'] = 'null'
+            try:
+                params['port'] = extra['port']
+            except KeyError:
+                params['port'] = 'null'
+            try:
+                params['weight'] = extra['weight']
+            except KeyError:
+                params['weight'] = 'null'
+
+        res = self.connection.request('/v2/domains/%s/records' % zone.id,
+                                      params=params,
+                                      method='POST')
+
+        return Record(id=res.object['domain_record']['id'],
+                    name=res.object['domain_record']['id'],
+                    type=type, data=data, zone=zone, driver=self, extra=extra)
+
+    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``
+        """
+        params = {}
+
+        res = self.connection.request('/v2/domains/%s' % zone.id,
+                                      params=params, method='DELETE')
+
+        return res.status == httplib.NO_CONTENT
+
+    def delete_record(self, record):
+        """
+        Delete a record.
+
+        :param record: Record to delete.
+        :type  record: :class:`Record`
+
+        :rtype: ``bool``
+        """
+        params = {}
+
+        res = self.connection.request('/v2/domains/%s/records/%s' % (
+                                      record.zone.id, record.id), params=params,
+                                      method='DELETE')
+        return res.status == httplib.NO_CONTENT
+
+# TODO: If there is a way to push this into libcloud.common.digitalocean
+#       instead of having it in libcloud.dns.digitalocean and
+#       libcloud.compute.digitalocean
+    def _paginated_request(self, url, obj):
+        """
+        Perform multiple calls in order to have a full list of elements when
+        the API responses are paginated.
+
+        :param url: API endpoint
+        :type url: ``str``
+
+        :param obj: Result object key
+        :type obj: ``str``
+
+        :return: ``list`` of API response objects
+        """
+        params = {}
+        data = self.connection.request(url)
+        try:
+            pages = data.object['links']['pages']['last'].split('=')[-1]
+            values = data.object[obj]
+            for page in range(2, int(pages) + 1):
+                params.update({'page': page})
+                new_data = self.connection.request(url, params=params)
+
+                more_values = new_data.object[obj]
+                for value in more_values:
+                    values.append(value)
+            data = values
+        except KeyError:  # No pages.
+            data = data.object[obj]
+
+        return data
+
+    def _to_record(self, data, zone=None):
+        extra = {'port' : data['port'], 'priority' : data['priority'],
+                 'weight' : data['weight']}
+        return Record(id=data['id'], name=data['name'],
+                      type=self._string_to_record_type(data['type']),
+                      data=data['data'], zone=zone, driver=self, extra=extra)
+
+    def _to_zone(self, data):
+        extra = {'zone_file' : data['zone_file'],}
+        return Zone(id=data['name'], domain=data['name'], type='master',
+                    ttl=data['ttl'], driver=self, extra=extra)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f50b8d42/libcloud/dns/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py
index dc80460..a6c92a8 100644
--- a/libcloud/dns/providers.py
+++ b/libcloud/dns/providers.py
@@ -35,6 +35,8 @@ DRIVERS = {
     Provider.GOOGLE: ('libcloud.dns.drivers.google', 'GoogleDNSDriver'),
     Provider.SOFTLAYER:
     ('libcloud.dns.drivers.softlayer', 'SoftLayerDNSDriver'),
+    Provider.DIGITAL_OCEAN:
+    ('libcloud.dns.drivers.digitalocean', 'DigitalOceanDNSDriver'),
     # Deprecated
     Provider.RACKSPACE_US:
     ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'),

http://git-wip-us.apache.org/repos/asf/libcloud/blob/f50b8d42/libcloud/dns/types.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py
index 32bceb5..cd1ae85 100644
--- a/libcloud/dns/types.py
+++ b/libcloud/dns/types.py
@@ -37,6 +37,7 @@ class Provider(object):
     GANDI = 'gandi'
     GOOGLE = 'google'
     SOFTLAYER = 'softlayer'
+    DIGITAL_OCEAN = 'digitalocean'
 
     # Deprecated
     RACKSPACE_US = 'rackspace_us'


Mime
View raw message