from .cfg_app import PpCfgAppError, PpConfigApplication
-__version__ = '0.1.1'
+__version__ = '0.2.0'
LOG = logging.getLogger(__name__)
_LIBRARY_NAME = "pp-pdns-api-client"
pass
+# =============================================================================
+class PDNSApiError(PpPDNSAppError):
+ """Base class for more complex exceptions"""
+ def __init__(self, resp, content, uri=None):
+ self.resp = resp
+ self.content = content
+ self.uri = uri
+
+
+# =============================================================================
+class PDNSApiNotAuthorizedError(PDNSApiError):
+ """The authorization information provided is not correct"""
+
+
+# =============================================================================
+class PDNSApiNotFoundError(PDNSApiError):
+ """The ProfitBricks entity was not found"""
+
+
+# =============================================================================
+class PDNSApiValidationError(PDNSApiError):
+ """The HTTP data provided is not valid"""
+
+
+# =============================================================================
+class PDNSApiRateLimitExceededError(PDNSApiError):
+ """The number of requests sent have exceeded the allowed API rate limit"""
+
+
+# =============================================================================
+class PDNSApiRequestError(PDNSApiError):
+ """Base error for request failures"""
+
+
+# =============================================================================
+class PDNSApiTimeoutError(PDNSApiRequestError):
+ """Raised when a request does not finish in the given time span."""
+
+
# =============================================================================
class PpPDNSApplication(PpConfigApplication):
default_api_port = 8081
default_api_servername = "localhost"
+ default_timeout = 20
# -------------------------------------------------------------------------
def __init__(
self._api_host = self.api_hosts['global']
self._api_port = self.default_api_port
self._api_servername = self.default_api_servername
- self._user_agent = '{}/{}'.format(_LIBRARY_NAME,self.version)
+ self._user_agent = '{}/{}'.format(_LIBRARY_NAME, self.version)
self._environment = 'global'
+ self._timeout = self.default_timeout
stems = []
if cfg_stems:
cfg_encoding=cfg_encoding, need_config_file=need_config_file,
)
+ self._user_agent = '{}/{}'.format(_LIBRARY_NAME, self.version)
+
# -----------------------------------------------------------
@property
def api_key(self):
raise PpPDNSAppError("Invalid user agent {!r} given.".format(value))
self._user_agent = str(value).strip()
+ # -----------------------------------------------------------
+ @property
+ def timeout(self):
+ "The timeout in seconds on requesting the PowerDNS API."
+ return self._timeout
+
+ @timeout.setter
+ def timeout(self, value):
+ v = int(value)
+ if v < 1:
+ raise PpPDNSAppError("Invalid timeout {!r} given.".format(value))
+ self._timeout = v
+
# -----------------------------------------------------------
@property
def environment(self):
res['api_keys'] = copy.copy(self.api_keys)
res['api_port'] = self.api_port
res['api_servername'] = self.api_servername
- res['default_api_servername'] = self.default_api_servername
res['default_api_port'] = self.default_api_port
+ res['default_api_servername'] = self.default_api_servername
+ res['default_timeout'] = self.default_timeout
res['environment'] = self.environment
+ res['timeout'] = self.timeout
res['user_agent'] = self.user_agent
return res
help=("Which port to connect to PowerDNS API, default: {}.".format(self.default_api_port)),
)
+ pdns_group.add_argument(
+ '-t', '--timeout',
+ metavar="SECS", type=int, dest='timeout', default=self.default_timeout,
+ help=("The timeout in seconds to request the PowerDNS API, default: {}.".format(
+ self.default_timeout)),
+ )
+
# -------------------------------------------------------------------------
def perform_arg_parser(self):
"""
if self.args.api_port:
self.api_port = self.args.api_port
+ if self.args.timeout:
+ self.timeout = self.args.timeout
+
# -------------------------------------------------------------------------
def pre_run(self):
"""
if self.verbose > 1:
LOG.debug("executing post_run() ...")
+ # -------------------------------------------------------------------------
+ def _build_url(self, path):
+
+ url = 'http://{}'.format(self.api_host)
+ if self.api_port != 80:
+ url += ':{}'.format(self.api_port)
+
+ url += '/api/v1' + path
+ LOG.debug("Used URL: {!r}".format(url))
+ return url
+
+ # -------------------------------------------------------------------------
+ def perform_request(self, path, method='GET', data=None, headers=None):
+ """Performing the underlying API request."""
+
+ if headers is None:
+ headers = dict()
+ headers['X-API-Key'] = self.api_key
+
+ url = self._build_url(path)
+ if self.verbose > 1:
+ LOG.debug("Request method: {!r}".format(method))
+ if data and self.verbose > 1:
+ data_out = "{!r}".format(data)
+ try:
+ data_out = json.loads(data)
+ except ValueError:
+ pass
+ else:
+ data_out = pp(data_out)
+ LOG.debug("Data:\n%s", data_out)
+
+ headers.update({'User-Agent': self.user_agent})
+ headers.update({'Content-Type': 'application/json'})
+ if self.verbose > 1:
+ LOG.debug("Headers:\n%s", pp(headers))
+
+ session = requests.Session()
+ response = session.request(method, url, data=data, headers=headers, timeout=self.timeout)
+
+ try:
+ if not response.ok:
+ err = response.json()
+ code = err['httpStatus']
+ msg = err['messages']
+ if response.status_code == 401:
+ raise PDNSApiNotAuthorizedError(code, msg, url)
+ if response.status_code == 404:
+ raise PDNSApiNotFoundError(code, msg, url)
+ if response.status_code == 422:
+ raise PDNSApiValidationError(code, msg, url)
+ if response.status_code == 429:
+ raise PDNSApiRateLimitExceededError(code, msg, url)
+ else:
+ raise PDNSApiError(code, msg, url)
+
+ except ValueError:
+ raise PpPDNSAppError('Failed to parse the response', response.text)
+
+ json_response = response.json()
+
+ if 'location' in response.headers:
+ json_response['requestId'] = self._request_id(response.headers)
+
+ return json_response
+
+
# =============================================================================