Source code for libcloud.common.gandi_live

# 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.
"""
Gandi Live driver base classes
"""

import json

from libcloud.common.base import ConnectionKey, JsonResponse
from libcloud.common.types import ProviderError

from libcloud.utils.py3 import httplib

__all__ = [
    'API_HOST',
    'GandiLiveBaseError',
    'JsonParseError',
    'ResourceNotFoundError',
    'InvalidRequestError',
    'ResourceConflictError',
    'GandiLiveResponse',
    'GandiLiveConnection',
    'BaseGandiLiveDriver',
]

API_HOST = 'dns.api.gandi.net'


[docs]class GandiLiveBaseError(ProviderError): """ Exception class for Gandi Live driver """ pass
[docs]class JsonParseError(GandiLiveBaseError): pass
# Example: # { # "code": 404, # "message": "Unknown zone", # "object": "LocalizedHTTPNotFound", # "cause": "Not Found" # }
[docs]class ResourceNotFoundError(GandiLiveBaseError): pass
# Example: # { # "code": 400, # "message": "zone or zone_uuid must be set", # "object": "HTTPBadRequest", # "cause": "No zone set.", # "errors": [ # { # "location": "body", # "name": "zone_uuid", # "description": "\"FAKEUUID\" is not a UUID" # } # ] # }
[docs]class InvalidRequestError(GandiLiveBaseError): pass
# Examples: # { # "code": 409, # "message": "Zone Testing already exists", # "object": "HTTPConflict", # "cause": "Duplicate Entry" # } # { # "code": 409, # "message": "The domain example.org already exists", # "object": "HTTPConflict", # "cause": "Duplicate Entry" # } # { # "code": 409, # "message": "This zone is still used by 1 domains", # "object": "HTTPConflict", # "cause": "In use" # }
[docs]class ResourceConflictError(GandiLiveBaseError): pass
[docs]class GandiLiveResponse(JsonResponse): """ A Base Gandi Live Response class to derive from. """
[docs] def success(self): """ Determine if our request was successful. For the Gandi Live response class, tag all responses as successful and raise appropriate Exceptions from parse_body. :return: C{True} """ return True
[docs] def parse_body(self): """ Parse the JSON response body, or raise exceptions as appropriate. :return: JSON dictionary :rtype: ``dict`` """ json_error = False try: body = json.loads(self.body) except Exception: # If there is both a JSON parsing error and an unsuccessful http # response (like a 404), we want to raise the http error and not # the JSON one, so don't raise JsonParseError here. body = self.body json_error = True # Service does not appear to return HTTP 202 Accepted for anything. valid_http_codes = [ httplib.OK, httplib.CREATED, httplib.NO_CONTENT ] if self.status in valid_http_codes: if json_error: raise JsonParseError(body, self.status) else: return body elif self.status == httplib.NOT_FOUND: message = self._get_error(body, json_error) raise ResourceNotFoundError(message, self.status) elif self.status == httplib.BAD_REQUEST: message = self._get_error(body, json_error) raise InvalidRequestError(message, self.status) elif self.status == httplib.CONFLICT: message = self._get_error(body, json_error) raise ResourceConflictError(message, self.status) else: message = self._get_error(body, json_error) raise GandiLiveBaseError(message, self.status)
# Errors are not described at all in Gandi's official documentation. # It appears when an error arises, a JSON object is returned along with # an HTTP 4xx class code. The object is structured as: # { # code: <code>, # object: <object>, # message: <message>, # cause: <cause>, # errors: [ # { # location: <error-location>, # name: <error-name>, # description: <error-description> # } # ] # } # where # <code> is a number equal to the HTTP response status code # <object> is a string with some internal name for the status code # <message> is a string detailing what the problem is # <cause> is a string that comes from a set of succinct problem summaries # errors is optional; if present: # <error-location> is a string for which part of the request to look in # <error-name> is a string naming the parameter # <error-description> is a string detailing what the problem is # Here we ignore object and combine message and cause along with an error # if one or more exists. def _get_error(self, body, json_error): """ Get the error code and message from a JSON response. Incorporate the first error if there are multiple errors. :param body: The body of the JSON response dictionary :type body: ``dict`` :return: String containing error message :rtype: ``str`` """ if not json_error and 'cause' in body: message = '%s: %s' % (body['cause'], body['message']) if 'errors' in body: err = body['errors'][0] message = '%s (%s in %s: %s)' % (message, err.get('location'), err.get('name'), err.get('description')) else: message = body return message
[docs]class GandiLiveConnection(ConnectionKey): """ Connection class for the Gandi Live driver """ responseCls = GandiLiveResponse host = API_HOST
[docs] def add_default_headers(self, headers): """ Returns default headers as a dictionary. """ headers["Content-Type"] = 'application/json' headers["X-Api-Key"] = self.key return headers
[docs] def encode_data(self, data): """Encode data to JSON""" return json.dumps(data)
[docs]class BaseGandiLiveDriver(object): """ Gandi Live base driver """ connectionCls = GandiLiveConnection name = 'GandiLive'