libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From GitBox <...@apache.org>
Subject [GitHub] [libcloud] c-w commented on a change in pull request #1292: [LIBCLOUD-1001] Update Cloudflare DNS driver to API v4
Date Mon, 15 Jul 2019 22:59:17 GMT
c-w commented on a change in pull request #1292: [LIBCLOUD-1001] Update Cloudflare DNS driver
to API v4
URL: https://github.com/apache/libcloud/pull/1292#discussion_r303670417
 
 

 ##########
 File path: libcloud/dns/drivers/cloudflare.py
 ##########
 @@ -141,290 +159,252 @@ class CloudFlareDNSDriver(DNSDriver):
         RecordType.URL: 'LOC'
     }
 
+    PAGE_SIZE = 20
+
     def iterate_zones(self):
-        # TODO: Support pagination
-        result = self.connection.request(action='zone_load_multi').object
-        zones = self._to_zones(data=result['response']['zones']['objs'])
+        def _iterate_zones(params):
+            url = '{}/zones'.format(API_BASE)
+
+            response = self.connection.request(url, params=params)
+
+            items = response.object['result']
+            zones = [self._to_zone(item) for item in items]
 
-        return zones
+            return zones
+
+        return self._paginate(_iterate_zones)
 
     def iterate_records(self, zone):
-        # TODO: Support pagination
-        params = {'z': zone.domain}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='rec_load_all', params=params)
-        data = resp.object['response']['recs']['objs']
-        records = self._to_records(zone=zone, data=data)
-        return records
+        def _iterate_records(params):
+            url = '{}/zones/{}/dns_records'.format(API_BASE, zone.id)
+
+            self.connection.set_context({'zone_id': zone.id})
+            response = self.connection.request(url, params=params)
+
+            items = response.object['result']
+            records = [self._to_record(zone, item) for item in items]
+
+            return records
+
+        return self._paginate(_iterate_records)
 
     def get_zone(self, zone_id):
-        # TODO: This is not efficient
-        zones = self.list_zones()
+        url = '{}/zones/{}'.format(API_BASE, zone_id)
+
+        self.connection.set_context({'zone_id': zone_id})
+        response = self.connection.request(url)
 
-        try:
-            zone = [z for z in zones if z.id == zone_id][0]
-        except IndexError:
-            raise ZoneDoesNotExistError(value='', driver=self, zone_id=zone_id)
+        item = response.object['result']
+        zone = self._to_zone(item)
 
         return zone
 
-    def create_record(self, name, zone, type, data, extra=None):
+    def create_zone(self, domain, type='master', ttl=None, extra=None):
+        """
+        @inherits: :class:`DNSDriver.create_zone`
+
+        Note that for users who have more than one account membership,
+        the id of the account in which to create the zone must be
+        specified via the ``extra`` key ``account``.
+
+        Note that for ``extra`` zone properties, only the ones specified in
+        ``ZONE_CREATE_ATTRIBUTES`` can be set at creation time. Additionally,
+        setting the ``ttl` property is not supported.
+        """
         extra = extra or {}
-        params = {'name': name, 'z': zone.domain, 'type': type,
-                  'content': data}
 
-        params['ttl'] = extra.get('ttl', 120)
+        account = extra.get('account')
+        if account is None:
+            memberships = self.ex_get_user_account_memberships()
+            memberships = list(itertools.islice(memberships, 2))
 
-        if 'priority' in extra:
-            # For MX and SRV records
-            params['prio'] = extra['priority']
+            if len(memberships) != 1:
+                raise ValueError('must specify account for zone')
 
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='rec_new', params=params)
-        item = resp.object['response']['rec']['obj']
-        record = self._to_record(zone=zone, item=item)
-        return record
+            account = memberships[0]['account']['id']
 
-    def update_record(self, record, name=None, type=None, data=None,
-                      extra=None):
-        extra = extra or {}
-        params = {'z': record.zone.domain, 'id': record.id}
+        url = '{}/zones'.format(API_BASE)
 
-        params['name'] = name or record.name
-        params['type'] = type or record.type
-        params['content'] = data or record.data
-        params['ttl'] = extra.get('ttl', None) or record.extra['ttl']
+        body = {
+            'name': domain,
+            'account': {
+                'id': account
+            },
+            'type': LIBCLOUD_TO_CLOUDFLARE_ZONE_TYPE[type]
+        }
 
-        self.connection.set_context({'zone_domain': record.zone.domain})
-        self.connection.set_context({'record_id': record.id})
-        resp = self.connection.request(action='rec_edit', params=params)
-        item = resp.object['response']['rec']['obj']
-        record = self._to_record(zone=record.zone, item=item)
-        return record
+        merge_valid_keys(body, ZONE_CREATE_ATTRIBUTES, extra)
 
-    def delete_record(self, record):
-        params = {'z': record.zone.domain, 'id': record.id}
-        self.connection.set_context({'zone_domain': record.zone.domain})
-        self.connection.set_context({'record_id': record.id})
-        resp = self.connection.request(action='rec_delete', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
-
-    def ex_get_zone_stats(self, zone, interval=30):
-        params = {'z': zone.domain, 'interval': interval}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='stats', params=params)
-        result = resp.object['response']['result']['objs'][0]
-        return result
-
-    def ex_zone_check(self, zones):
-        zone_domains = [zone.domain for zone in zones]
-        zone_domains = ','.join(zone_domains)
-        params = {'zones': zone_domains}
-        resp = self.connection.request(action='zone_check', params=params)
-        result = resp.object['response']['zones']
-        return result
-
-    def ex_get_ip_threat_score(self, ip):
-        """
-        Retrieve current threat score for a given IP. Note that scores are on
-        a logarithmic scale, where a higher score indicates a higher threat.
-        """
-        params = {'ip': ip}
-        resp = self.connection.request(action='ip_lkup', params=params)
-        result = resp.object['response']
-        return result
+        response = self.connection.request(url, data=body, method='POST')
 
-    def ex_get_zone_settings(self, zone):
-        """
-        Retrieve all current settings for a given zone.
-        """
-        params = {'z': zone.domain}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='zone_settings', params=params)
-        result = resp.object['response']['result']['objs'][0]
-        return result
+        item = response.object['result']
+        zone = self._to_zone(item)
 
-    def ex_set_zone_security_level(self, zone, level):
-        """
-        Set the zone Basic Security Level to I'M UNDER ATTACK! / HIGH /
-        MEDIUM / LOW / ESSENTIALLY OFF.
+        return zone
 
-        :param level: Security level. Valid values are: help, high, med, low,
-                      eoff.
-        :type level: ``str``
+    def update_zone(self, zone, domain, type='master', ttl=None, extra=None):
         """
-        params = {'z': zone.domain, 'v': level}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='sec_lvl', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        @inherits: :class:`DNSDriver.update_zone`
 
-    def ex_set_zone_cache_level(self, zone, level):
+        Note that the ``zone``, ``type`` and ``ttl`` properties can't be
+        updated. The only updatable properties are the ``extra`` zone
+        properties specified in ``ZONE_UPDATE_ATTRIBUTES``. Only one property
+        may be updated at a time. Any non-updatable properties are ignored.
         """
-        Set the zone caching level.
+        body = merge_valid_keys({}, ZONE_UPDATE_ATTRIBUTES, extra)
+        if len(body) != 1:
+            return zone
 
-        :param level: Caching level. Valid values are: agg (aggresive), basic.
-        :type level: ``str``
-        """
-        params = {'z': zone.domain, 'v': level}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='cache_lvl', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        url = '{}/zones/{}'.format(API_BASE, zone.id)
 
-    def ex_enable_development_mode(self, zone):
-        """
-        Enable development mode. When Development Mode is on the cache is
-        bypassed. Development mode remains on for 3 hours or until when it is
-        toggled back off.
-        """
-        params = {'z': zone.domain, 'v': 1}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='devmode', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        self.connection.set_context({'zone_id': zone.id})
+        response = self.connection.request(url, data=body, method='PATCH')
 
-    def ex_disable_development_mode(self, zone):
-        """
-        Disable development mode.
-        """
-        params = {'z': zone.domain, 'v': 0}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='devmode', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        item = response.object['result']
+        zone = self._to_zone(item)
 
-    def ex_purge_cached_files(self, zone):
-        """
-        Purge CloudFlare of any cached files.
-        """
-        params = {'z': zone.domain, 'v': 1}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='fpurge_ts', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        return zone
 
-    def ex_purge_cached_file(self, zone, url):
-        """
-        Purge single file from CloudFlare's cache.
+    def delete_zone(self, zone):
+        url = '{}/zones/{}'.format(API_BASE, zone.id)
 
-        :param url: URL to the file to purge from cache.
-        :type url: ``str``
-        """
-        params = {'z': zone.domain, 'url': url}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='zone_file_purge', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        self.connection.set_context({'zone_id': zone.id})
+        response = self.connection.request(url, method='DELETE')
 
-    def ex_whitelist_ip(self, zone, ip):
-        """
-        Whitelist the provided IP.
-        """
-        params = {'z': zone.domain, 'key': ip}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='wl', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        item = response.object.get('result', {}).get('id')
+        is_deleted = item == zone.id
 
-    def ex_blacklist_ip(self, zone, ip):
-        """
-        Blacklist the provided IP.
-        """
-        params = {'z': zone.domain, 'key': ip}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='ban', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        return is_deleted
 
-    def ex_unlist_ip(self, zone, ip):
-        """
-        Remove provided ip from the whitelist and blacklist.
-        """
-        params = {'z': zone.domain, 'key': ip}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='nul', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+    def get_record(self, zone_id, record_id):
+        zone = self.get_zone(zone_id)
+
+        url = '{}/zones/{}/dns_records/{}'.format(API_BASE, zone.id,
+                                                  record_id)
 
-    def ex_enable_ipv6_support(self, zone):
+        self.connection.set_context({'record_id': record_id})
+        response = self.connection.request(url)
+
+        item = response.object['result']
+        record = self._to_record(zone, item)
+
+        return record
+
+    def create_record(self, name, zone, type, data, extra=None):
         """
-        Enable IPv6 support for the provided zone.
+        @inherits: :class:`DNSDriver.create_record`
+
+        Note that for ``extra`` record properties, only the ones specified in
+        ``RECORD_CREATE_ATTRIBUTES`` can be set at creation time. Any
+        non-settable properties are ignored.
         """
-        params = {'z': zone.domain, 'v': 3}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='ipv46', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        url = '{}/zones/{}/dns_records'.format(API_BASE, zone.id)
+
+        body = {
+            'type': type,
+            'name': name,
+            'content': data,
+        }
+
+        merge_valid_keys(body, RECORD_CREATE_ATTRIBUTES, extra)
+
+        self.connection.set_context({'zone_id': zone.id})
+        response = self.connection.request(url, data=body, method='POST')
 
-    def ex_disable_ipv6_support(self, zone):
+        item = response.object['result']
+        record = self._to_record(zone, item)
+
+        return record
+
+    def update_record(self, record, name=None, type=None, data=None,
+                      extra=None):
         """
-        Disable IPv6 support for the provided zone.
+        @inherits: :class:`DNSDriver.update_record`
+
+        Note that for ``extra`` record properties, only the ones specified in
+        ``RECORD_UPDATE_ATTRIBUTES`` can be updated. Any non-updatable
+        properties are ignored.
         """
-        params = {'z': zone.domain, 'v': 0}
-        self.connection.set_context({'zone_domain': zone.domain})
-        resp = self.connection.request(action='ipv46', params=params)
-        result = resp.object
-        return result.get('result', None) == 'success'
+        url = '{}/zones/{}/dns_records/{}'.format(API_BASE, record.zone.id,
+                                                  record.id)
+
+        body = {
+            'type': record.type if type is None else type,
+            'name': record.name if name is None else name,
+            'content': record.data if data is None else data,
+            'extra': record.extra or {},
+        }
 
-    def _to_zones(self, data):
-        zones = []
+        merge_valid_keys(body['extra'], RECORD_UPDATE_ATTRIBUTES, extra)
 
-        for item in data:
-            zone = self._to_zone(item=item)
-            zones.append(zone)
+        self.connection.set_context({'record_id': record.id})
+        response = self.connection.request(url, data=body, method='PUT')
 
-        return zones
+        item = response.object['result']
+        record = self._to_record(record.zone, item)
 
-    def _to_zone(self, item):
-        type = 'master'
-
-        extra = {}
-        extra['props'] = item.get('props', {})
-        extra['confirm_code'] = item.get('confirm_code', {})
-        extra['allow'] = item.get('allow', {})
-        for attribute in ZONE_EXTRA_ATTRIBUTES:
-            value = item.get(attribute, None)
-            extra[attribute] = value
-
-        zone = Zone(id=str(item['zone_id']), domain=item['zone_name'],
-                    type=type, ttl=None, driver=self, extra=extra)
-        return zone
+        return record
 
-    def _to_records(self, zone, data):
-        records = []
+    def delete_record(self, record):
+        url = '{}/zones/{}/dns_records/{}'.format(API_BASE, record.zone.id,
+                                                  record.id)
 
-        for item in data:
-            record = self._to_record(zone=zone, item=item)
-            records.append(record)
+        self.connection.set_context({'record_id': record.id})
+        response = self.connection.request(url, method='DELETE')
 
-        return records
+        item = response.object.get('result', {}).get('id')
+        is_deleted = item == record.id
 
-    def _to_record(self, zone, item):
-        name = self._get_record_name(item=item)
-        type = item['type']
-        data = item['content']
-
-        if item.get('ttl', None):
-            ttl = int(item['ttl'])
-        else:
-            ttl = None
-
-        extra = {}
-        extra['ttl'] = ttl
-        extra['props'] = item.get('props', {})
-        for attribute in RECORD_EXTRA_ATTRIBUTES:
-            value = item.get(attribute, None)
-            extra[attribute] = value
-
-        record = Record(id=str(item['rec_id']), name=name, type=type,
-                        data=data, zone=zone, driver=self, ttl=ttl,
-                        extra=extra)
-        return record
+        return is_deleted
+
+    def ex_get_user_account_memberships(self):
+        def _ex_get_user_account_memberships(params):
+            url = '{}/memberships'.format(API_BASE)
 
-    def _get_record_name(self, item):
-        name = item['name'].replace('.' + item['zone_name'], '') or None
-        if name:
-            name = name.replace(item['zone_name'], '') or None
-        return name
+            response = self.connection.request(url, params=params)
+            return response.object['result']
+
+        return self._paginate(_ex_get_user_account_memberships)
+
+    def _to_zone(self, item):
+        return Zone(
+            id=item['id'],
+            domain=item['name'],
+            type=CLOUDFLARE_TO_LIBCLOUD_ZONE_TYPE[item['type']],
+            ttl=None,
+            driver=self,
+            extra={key: item.get(key) for key in ZONE_EXTRA_ATTRIBUTES},
+        )
+
+    def _to_record(self, zone, item):
+        name = item['name']
+        name = name.replace('.' + item['zone_name'], '')
+        name = name.replace(item['zone_name'], '')
+        name = name or None
+
+        ttl = item.get('ttl')
+        if ttl is not None:
+            ttl = int(ttl)
+
+        return Record(
+            id=item['id'],
+            name=name,
+            type=item['type'],
+            data=item['content'],
+            zone=zone,
+            driver=self,
+            ttl=ttl,
+            extra={key: item.get(key) for key in RECORD_EXTRA_ATTRIBUTES},
+        )
+
+    def _paginate(self, get_page):
+        for page in itertools.count(start=1):
+            params = {'page': page, 'per_page': self.PAGE_SIZE}
+
+            items = get_page(params)
+
+            for item in items:
+                yield item
+
+            if len(items) < self.PAGE_SIZE:
 
 Review comment:
   Good catch. Used the `result_info` object if it's present to avoid the extra HTTP request
in 828b471.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

Mime
View raw message