mirror of
https://github.com/ansible/awx.git
synced 2026-03-04 18:21:03 -03:30
292 lines
9.5 KiB
Python
292 lines
9.5 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2010 Jacob Kaplan-Moss
|
|
# Copyright 2011 OpenStack LLC.
|
|
# Copyright 2011 Piston Cloud Computing, Inc.
|
|
# Copyright (c)2012 Rackspace US, Inc.
|
|
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed 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.
|
|
"""
|
|
OpenStack Client interface. Handles the REST calls and responses.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import json
|
|
import logging
|
|
import requests
|
|
import time
|
|
import six.moves.urllib as urllib
|
|
|
|
import pyrax
|
|
import pyrax.exceptions as exc
|
|
|
|
|
|
def _safe_quote(val):
|
|
"""
|
|
Unicode values will raise a KeyError, so catch those and encode in UTF-8.
|
|
"""
|
|
SAFE_QUOTE_CHARS = "/.?&=,"
|
|
try:
|
|
ret = urllib.parse.quote(val, safe=SAFE_QUOTE_CHARS)
|
|
except KeyError:
|
|
ret = urllib.parse.quote(val.encode("utf-8"), safe=SAFE_QUOTE_CHARS)
|
|
return ret
|
|
|
|
|
|
class BaseClient(object):
|
|
"""
|
|
The base class for all pyrax clients.
|
|
"""
|
|
# This will get set by pyrax when the service is started.
|
|
user_agent = None
|
|
# Each client subclass should set their own name.
|
|
name = "base"
|
|
|
|
def __init__(self, identity, region_name=None, endpoint_type=None,
|
|
management_url=None, service_name=None, timings=False,
|
|
verify_ssl=True, http_log_debug=False, timeout=None):
|
|
self.version = "v1.1"
|
|
self.identity = identity
|
|
self.region_name = region_name
|
|
self.endpoint_type = endpoint_type
|
|
self.service_name = service_name
|
|
self.management_url = management_url
|
|
self.timings = timings
|
|
self.verify_ssl = verify_ssl
|
|
self.http_log_debug = http_log_debug
|
|
self.timeout = timeout
|
|
self.times = [] # [("item", starttime, endtime), ...]
|
|
|
|
self._manager = None
|
|
# Hook method for subclasses to create their manager instance
|
|
# without having to override __init__().
|
|
self._configure_manager()
|
|
|
|
|
|
def _configure_manager(self):
|
|
"""
|
|
This must be overridden in base classes to create
|
|
the required manager class and configure it as needed.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
# The next 6 methods are simple pass-through to the manager.
|
|
def list(self, limit=None, marker=None):
|
|
"""
|
|
Returns a list of resource objects. Pagination is supported through the
|
|
optional 'marker' and 'limit' parameters.
|
|
"""
|
|
return self._manager.list(limit=limit, marker=marker)
|
|
|
|
|
|
def get(self, item):
|
|
"""Gets a specific resource."""
|
|
return self._manager.get(item)
|
|
|
|
|
|
def create(self, *args, **kwargs):
|
|
"""Creates a new resource."""
|
|
return self._manager.create(*args, **kwargs)
|
|
|
|
|
|
def delete(self, item):
|
|
"""Deletes a specific resource."""
|
|
return self._manager.delete(item)
|
|
|
|
|
|
def find(self, **kwargs):
|
|
"""
|
|
Finds a single item with attributes matching ``**kwargs``.
|
|
|
|
This isn't very efficient: it loads the entire list then filters on
|
|
the Python side.
|
|
"""
|
|
return self._manager.find(**kwargs)
|
|
|
|
|
|
def findall(self, **kwargs):
|
|
"""
|
|
Finds all items with attributes matching ``**kwargs``.
|
|
|
|
This isn't very efficient: it loads the entire list then filters on
|
|
the Python side.
|
|
"""
|
|
return self._manager.findall(**kwargs)
|
|
|
|
|
|
def unauthenticate(self):
|
|
"""Clears all of our authentication information."""
|
|
self.identity.unauthenticate()
|
|
|
|
|
|
def get_timings(self):
|
|
"""Returns a list of all execution timings."""
|
|
return self.times
|
|
|
|
|
|
def reset_timings(self):
|
|
"""Clears the timing history."""
|
|
self.times = []
|
|
|
|
|
|
def get_limits(self):
|
|
"""
|
|
Returns a dict with the resource and rate limits for the account.
|
|
"""
|
|
resp, resp_body = self.method_get("/limits")
|
|
return resp_body
|
|
|
|
|
|
def _add_custom_headers(self, dct):
|
|
"""
|
|
Clients for some services must add headers that are required for that
|
|
service. This is a hook method to allow for such customization.
|
|
|
|
If a client needs to add a special header, the 'dct' parameter is a
|
|
dictionary of headers. Add the header(s) and their values as key/value
|
|
pairs to the 'dct'.
|
|
"""
|
|
pass
|
|
|
|
|
|
def request(self, uri, method, *args, **kwargs):
|
|
"""
|
|
Formats the request into a dict representing the headers
|
|
and body that will be used to make the API call.
|
|
"""
|
|
if self.timeout:
|
|
kwargs["timeout"] = self.timeout
|
|
kwargs["verify"] = self.verify_ssl
|
|
kwargs.setdefault("headers", kwargs.get("headers", {}))
|
|
kwargs["headers"]["User-Agent"] = self.user_agent
|
|
kwargs["headers"]["Accept"] = "application/json"
|
|
if ("body" in kwargs) or ("data" in kwargs):
|
|
if "Content-Type" not in kwargs["headers"]:
|
|
kwargs["headers"]["Content-Type"] = "application/json"
|
|
elif kwargs["headers"]["Content-Type"] is None:
|
|
del kwargs["headers"]["Content-Type"]
|
|
# Allow subclasses to add their own headers
|
|
self._add_custom_headers(kwargs["headers"])
|
|
resp, body = pyrax.http.request(method, uri, *args, **kwargs)
|
|
if resp.status_code >= 400:
|
|
raise exc.from_response(resp, body)
|
|
return resp, body
|
|
|
|
|
|
def _time_request(self, uri, method, **kwargs):
|
|
"""Wraps the request call and records the elapsed time."""
|
|
start_time = time.time()
|
|
resp, body = self.request(uri, method, **kwargs)
|
|
self.times.append(("%s %s" % (method, uri),
|
|
start_time, time.time()))
|
|
return resp, body
|
|
|
|
|
|
def _api_request(self, uri, method, **kwargs):
|
|
"""
|
|
Manages the request by adding any auth information, and retries
|
|
the request after authenticating if the initial request returned
|
|
and Unauthorized exception.
|
|
"""
|
|
id_svc = self.identity
|
|
if not all((self.management_url, id_svc.token, id_svc.tenant_id)):
|
|
id_svc.authenticate()
|
|
|
|
if not self.management_url:
|
|
# We've authenticated but no management_url has been set. This
|
|
# indicates that the service is not available.
|
|
raise exc.ServiceNotAvailable("The '%s' service is not available."
|
|
% self)
|
|
if uri.startswith("http"):
|
|
parsed = list(urllib.parse.urlparse(uri))
|
|
for pos, item in enumerate(parsed):
|
|
if pos < 2:
|
|
# Don't escape the scheme or netloc
|
|
continue
|
|
parsed[pos] = _safe_quote(parsed[pos])
|
|
safe_uri = urllib.parse.urlunparse(parsed)
|
|
else:
|
|
safe_uri = "%s%s" % (self.management_url, _safe_quote(uri))
|
|
# Perform the request once. If we get a 401 back then it
|
|
# might be because the auth token expired, so try to
|
|
# re-authenticate and try again. If it still fails, bail.
|
|
try:
|
|
kwargs.setdefault("headers", {})["X-Auth-Token"] = id_svc.token
|
|
if id_svc.tenant_id:
|
|
kwargs["headers"]["X-Auth-Project-Id"] = id_svc.tenant_id
|
|
resp, body = self._time_request(safe_uri, method, **kwargs)
|
|
return resp, body
|
|
except exc.Unauthorized as ex:
|
|
try:
|
|
id_svc.authenticate()
|
|
kwargs["headers"]["X-Auth-Token"] = id_svc.token
|
|
resp, body = self._time_request(safe_uri, method, **kwargs)
|
|
return resp, body
|
|
except exc.Unauthorized:
|
|
raise ex
|
|
|
|
|
|
def method_head(self, uri, **kwargs):
|
|
"""Method used to make HEAD requests."""
|
|
return self._api_request(uri, "HEAD", **kwargs)
|
|
|
|
|
|
def method_get(self, uri, **kwargs):
|
|
"""Method used to make GET requests."""
|
|
return self._api_request(uri, "GET", **kwargs)
|
|
|
|
|
|
def method_post(self, uri, **kwargs):
|
|
"""Method used to make POST requests."""
|
|
return self._api_request(uri, "POST", **kwargs)
|
|
|
|
|
|
def method_put(self, uri, **kwargs):
|
|
"""Method used to make PUT requests."""
|
|
return self._api_request(uri, "PUT", **kwargs)
|
|
|
|
|
|
def method_delete(self, uri, **kwargs):
|
|
"""Method used to make DELETE requests."""
|
|
return self._api_request(uri, "DELETE", **kwargs)
|
|
|
|
|
|
def method_patch(self, uri, **kwargs):
|
|
"""Method used to make PATCH requests."""
|
|
return self._api_request(uri, "PATCH", **kwargs)
|
|
|
|
|
|
def authenticate(self):
|
|
"""
|
|
Handles all aspects of authentication against the cloud provider.
|
|
Currently this has only been tested with Rackspace auth; if you wish
|
|
to use this library with a different OpenStack provider, you may have
|
|
to modify this method. Please post your findings on GitHub so that
|
|
others can benefit.
|
|
"""
|
|
return self.identity.authenticate()
|
|
|
|
|
|
@property
|
|
def projectid(self):
|
|
"""
|
|
The older parts of this code used 'projectid'; this wraps that
|
|
reference.
|
|
"""
|
|
return self.identity.tenant_id
|