| # 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, parse_qs, urlparse |
| |
| from libcloud.common.base import BaseDriver |
| from libcloud.common.base import ConnectionKey |
| from libcloud.common.base import JsonResponse |
| from libcloud.common.types import LibcloudError, InvalidCredsError |
| |
| __all__ = [ |
| 'DigitalOcean_v2_Response', |
| 'DigitalOcean_v2_Connection', |
| 'DigitalOceanBaseDriver' |
| ] |
| |
| |
| class DigitalOcean_v1_Error(LibcloudError): |
| """ |
| Exception for when attempting to use version 1 |
| of the DigitalOcean API which is no longer |
| supported. |
| """ |
| |
| def __init__(self, |
| value=('Driver no longer supported: Version 1 of the ' |
| 'DigitalOcean API reached end of life on November 9, ' |
| '2015. Use the v2 driver. Please visit: ' |
| 'https://developers.digitalocean.com/documentation/changelog/api-v1/sunsetting-api-v1/'), # noqa: E501 |
| driver=None): |
| super(DigitalOcean_v1_Error, self).__init__(value, driver=driver) |
| |
| |
| 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 |
| |
| def add_default_params(self, params): |
| """ |
| Add parameters that are necessary for every request |
| |
| This method adds ``per_page`` to the request to reduce the total |
| number of paginated requests to the API. |
| """ |
| params['per_page'] = self.driver.ex_per_page |
| return params |
| |
| |
| class DigitalOceanConnection(DigitalOcean_v2_Connection): |
| """ |
| Connection class for the DigitalOcean driver. |
| """ |
| pass |
| |
| |
| class DigitalOceanResponse(DigitalOcean_v2_Response): |
| pass |
| |
| |
| class DigitalOceanBaseDriver(BaseDriver): |
| """ |
| DigitalOcean BaseDriver |
| """ |
| name = 'DigitalOcean' |
| website = 'https://www.digitalocean.com' |
| |
| def __new__(cls, key, secret=None, api_version='v2', **kwargs): |
| if cls is DigitalOceanBaseDriver: |
| if api_version == 'v1' or secret is not None: |
| raise DigitalOcean_v1_Error() |
| elif api_version == 'v2': |
| cls = DigitalOcean_v2_BaseDriver |
| else: |
| raise NotImplementedError('Unsupported API version: %s' % |
| (api_version)) |
| return super(DigitalOceanBaseDriver, cls).__new__(cls, **kwargs) |
| |
| def ex_account_info(self): |
| raise NotImplementedError( |
| 'ex_account_info not implemented for this driver') |
| |
| def ex_list_events(self): |
| raise NotImplementedError( |
| 'ex_list_events not implemented for this driver') |
| |
| def ex_get_event(self, event_id): |
| raise NotImplementedError( |
| 'ex_get_event not implemented for this driver') |
| |
| def _paginated_request(self, url, obj): |
| raise NotImplementedError( |
| '_paginated_requests not implemented for this driver') |
| |
| |
| class DigitalOcean_v2_BaseDriver(DigitalOceanBaseDriver): |
| """ |
| DigitalOcean BaseDriver using v2 of the API. |
| |
| Supports `ex_per_page` ``int`` value keyword parameter to adjust per page |
| requests against the API. |
| """ |
| connectionCls = DigitalOcean_v2_Connection |
| |
| def __init__(self, key, secret=None, secure=True, host=None, port=None, |
| api_version=None, region=None, ex_per_page=200, **kwargs): |
| self.ex_per_page = ex_per_page |
| super(DigitalOcean_v2_BaseDriver, self).__init__(key, **kwargs) |
| |
| def ex_account_info(self): |
| return self.connection.request('/v2/account').object['account'] |
| |
| def ex_list_events(self): |
| return self._paginated_request('/v2/actions', 'actions') |
| |
| def ex_get_event(self, event_id): |
| """ |
| Get an event object |
| |
| :param event_id: Event id (required) |
| :type event_id: ``str`` |
| """ |
| params = {} |
| return self.connection.request('/v2/actions/%s' % event_id, |
| params=params).object['action'] |
| |
| 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 |
| :rtype: ``list`` |
| """ |
| params = {} |
| data = self.connection.request(url) |
| try: |
| query = urlparse.urlparse(data.object['links']['pages']['last']) |
| # The query[4] references the query parameters from the url |
| pages = parse_qs(query[4])['page'][0] |
| 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 |