Module ciocore.api_client¶
Functions¶
read_conductor_credentials¶
Source
def read_conductor_credentials(use_api_key=False):
cfg = config.config().config
logger.debug("Reading conductor credentials...")
if use_api_key:
if not cfg.get("api_key"):
use_api_key = False
if use_api_key and not cfg["api_key"].get("client_id"):
use_api_key = False
logger.debug("use_api_key = %s" % use_api_key)
creds_file = get_creds_path(use_api_key)
logger.debug("Creds file is %s" % creds_file)
logger.debug("Auth url is %s" % cfg["url"])
if not os.path.exists(creds_file):
if use_api_key:
if not cfg["api_key"]:
logger.debug("Attempted to use API key, but no api key in in config!")
return None
# Exchange the API key for a bearer token
logger.debug("Attempting to get API key bearer token")
get_api_key_bearer_token(creds_file)
else:
auth.run(creds_file, cfg["url"])
if not os.path.exists(creds_file):
return None
logger.debug("Reading credentials file...")
with open(creds_file) as fp:
file_contents = json.loads(fp.read())
expiration = file_contents.get("expiration")
same_domain = creds_same_domain(file_contents)
if same_domain and expiration and expiration >= int(time.time()):
return file_contents["access_token"]
logger.debug("Credentials have expired or are from a different domain")
if use_api_key:
logger.debug("Refreshing API key bearer token!")
get_api_key_bearer_token(creds_file)
else:
logger.debug("Sending to auth page...")
auth.run(creds_file, cfg["url"])
# Re-read the creds file, since it has been re-upped
with open(creds_file) as fp:
file_contents = json.loads(fp.read())
return file_contents["access_token"]
read_conductor_credentials(use_api_key=False)
-
Read the conductor credentials file, if it exists. This will contain a bearer token from either the user or the API key (if that's desired). If the credentials file doesn't exist, or is expired, or is from a different domain, try and fetch a new one in the API key scenario or prompt the user to log in. Args: use_api_key: Whether or not to use the API key
Returns: A Bearer token in the event of a success or None if things couldn't get figured out
get_api_key_bearer_token¶
Source
def get_api_key_bearer_token(creds_file=None):
cfg = config.config().config
url = "{}/api/oauth_jwt".format(cfg["url"])
response = requests.get(
url,
params={
"grant_type": "client_credentials",
"scope": "owner admin user",
"client_id": cfg["api_key"]["client_id"],
"client_secret": cfg["api_key"]["private_key"],
},
)
if response.status_code == 200:
response_dict = json.loads(response.text)
credentials_dict = {
"access_token": response_dict["access_token"],
"token_type": "Bearer",
"expiration": int(time.time()) + int(response_dict["expires_in"]),
"scope": "user admin owner",
}
if not creds_file:
return credentials_dict
if not os.path.exists(os.path.dirname(creds_file)):
os.makedirs(os.path.dirname(creds_file))
with open(creds_file, "w") as fp:
fp.write(json.dumps(credentials_dict))
return
get_api_key_bearer_token(creds_file=None)
get_creds_path¶
Source
def get_creds_path(api_key=False):
creds_dir = os.path.join(os.path.expanduser("~"), ".config", "conductor")
if api_key:
creds_file = os.path.join(creds_dir, "api_key_credentials")
else:
creds_file = os.path.join(creds_dir, "credentials")
return creds_file
get_creds_path(api_key=False)
get_bearer_token¶
Source
def get_bearer_token(refresh=False):
return read_conductor_credentials(True)
get_bearer_token(refresh=False)
-
Return the bearer token.
TODO: Thread safe multiproc caching, like it used to be pre-python3.7.
creds_same_domain¶
Source
def creds_same_domain(creds):
cfg = config.config().config
token = creds.get("access_token")
if not token:
return False
decoded = jwt.decode(creds["access_token"], verify=False)
audience_domain = decoded.get("aud")
return (
audience_domain
and audience_domain.rpartition("/")[-1] == cfg["api_url"].rpartition("/")[-1]
)
creds_same_domain(creds)
account_id_from_jwt¶
Source
def account_id_from_jwt(token):
payload = jwt.decode(token, verify=False)
return payload.get("account")
account_id_from_jwt(token)
- Fetch the accounts id from a jwt token value.
account_name_from_jwt¶
Source
def account_name_from_jwt(token):
cfg = config.config().config
account_id = account_id_from_jwt(token)
if account_id:
url = "%s/api/v1/accounts/%s" % (cfg["api_url"], account_id)
response = requests.get(url, headers={"authorization": "Bearer %s" % token})
if response.status_code == 200:
response_dict = json.loads(response.text)
return response_dict["data"]["name"]
return None
account_name_from_jwt(token)
- Fetch the accounts name from a jwt token value.
request_instance_types¶
Source
def request_instance_types(as_dict=False):
api = ApiClient()
response, response_code = api.make_request(
"api/v1/instance-types", use_api_key=True, raise_on_error=False
)
if response_code not in (200,):
msg = "Failed to get instance types"
msg += "\nError %s ...\n%s" % (response_code, response)
raise Exception(msg)
instance_types = json.loads(response).get("data", [])
logger.debug("Found available instance types: %s", instance_types)
if as_dict:
return dict([(instance["description"], instance) for instance in instance_types])
return instance_types
request_instance_types(as_dict=False)
- Get the list of available instances types.
request_projects¶
Source
def request_projects(statuses=("active",)):
api = ApiClient()
logger.debug("statuses: %s", statuses)
uri = "api/v1/projects/"
response, response_code = api.make_request(
uri_path=uri, verb="GET", raise_on_error=False, use_api_key=True
)
logger.debug("response: %s", response)
logger.debug("response: %s", response_code)
if response_code not in [200]:
msg = "Failed to get available projects from Conductor"
msg += "\nError %s ...\n%s" % (response_code, response)
raise Exception(msg)
projects = []
# Filter for only projects of the proper status
for project in json.loads(response).get("data") or []:
if not statuses or project.get("status") in statuses:
projects.append(project["name"])
return projects
request_projects(statuses=('active',))
- Query Conductor for all client Projects that are in the given state(s)
request_software_packages¶
Source
def request_software_packages():
api = ApiClient()
uri = "api/v1/ee/packages?all=true,"
response, response_code = api.make_request(
uri_path=uri, verb="GET", raise_on_error=False, use_api_key=True
)
if response_code not in [200]:
msg = "Failed to get software packages for latest sidecar"
msg += "\nError %s ...\n%s" % (response_code, response)
raise Exception(msg)
return json.loads(response).get("data", [])
request_software_packages()
- Query Conductor for all software packages for the currently available sidecar.
Classes¶
ApiClient¶
Source
def __init__(self):
logger.debug("")
ApiClient()
-
Class variables¶
http_verbs¶
http_verbs
:USER_AGENT_TEMPLATE¶
USER_AGENT_TEMPLATE
:USER_AGENT_MAX_PATH_LENGTH¶
USER_AGENT_MAX_PATH_LENGTH
:user_agent_header¶
user_agent_header
:Static methods¶
register_client¶
Source
@classmethod def register_client(cls, client_name, client_version=None): cls.user_agent_header = cls._get_user_agent_header(client_name, client_version)
register_client(client_name, client_version=None)
:Methods¶
make_prepared_request¶
Source
def make_prepared_request( self, verb, url, headers=None, params=None, json=None, data=None, stream=False, remove_headers_list=None, raise_on_error=True, tries=5, ): req = requests.Request( method=verb, url=url, headers=headers, params=params, json=json, data=data, ) prepped = req.prepare() if remove_headers_list: for header in remove_headers_list: prepped.headers.pop(header, None) # Create a retry wrapper function retry_wrapper = common.DecRetry(retry_exceptions=CONNECTION_EXCEPTIONS, tries=tries) # requests sessions potentially not thread-safe, but need to potentially removed enforced # headers by using a prepared request.create which can only be done through an # request.Session object. Create Session object per call of make_prepared_request, it will # not benefit from connection pooling reuse. https://github.com/psf/requests/issues/1871 session = requests.Session() # wrap the request function with the retry wrapper wrapped_func = retry_wrapper(session.send) # call the wrapped request function response = wrapped_func(prepped, stream=stream) logger.debug("verb: %s", prepped.method) logger.debug("url: %s", prepped.url) logger.debug("headers: %s", prepped.headers) logger.debug("params: %s", req.params) # trigger an exception to be raised for 4XX or 5XX http responses if raise_on_error: response.raise_for_status() return response
make_prepared_request(self, verb, url, headers=None, params=None, json=None, data=None, stream=False, remove_headers_list=None, raise_on_error=True, tries=5)
-
Primarily used to removed enforced headers by requests.Request. Requests 2.x will add Transfer-Encoding: chunked with file like object that is 0 bytes, causing s3 failures (501) - https://github.com/psf/requests/issues/4215#issuecomment-319521235
To get around this bug make_prepared_request has functionality to remove the enforced header that would occur when using requests.request(...). Requests 3.x resolves this issue, when client is built to use Requests 3.x this function can be deprecated.
args: verb: (str) of HTTP verbs url: (str) url headers: (dict) params: (dict) json: (dict) data: (varies) stream: (bool) remove_headers_list: list of headers to remove i.e ["Transfer-Encoding"] raise_on_error: (bool) tries: (int) number of attempts to perform request
return: request.Response
make_request¶
Source
def make_request( self, uri_path="/", headers=None, params=None, data=None, verb=None, conductor_url=None, raise_on_error=True, tries=5, use_api_key=False ): cfg = config.config().config # TODO: set Content Content-Type to json if data arg if not headers: headers = {"Content-Type": "application/json", "Accept": "application/json"} logger.debug("read_conductor_credentials({})".format(use_api_key)) bearer_token = read_conductor_credentials(use_api_key) if not bearer_token: raise Exception("Error: Could not get conductor credentials!") headers["Authorization"] = "Bearer %s" % bearer_token if not ApiClient.user_agent_header: self.register_client("ciocore") headers["User-Agent"] = ApiClient.user_agent_header # Construct URL if not conductor_url: conductor_url = parse.urljoin(cfg["url"], uri_path) if not verb: if data: verb = "POST" else: verb = "GET" assert verb in self.http_verbs, "Invalid http verb: %s" % verb # Create a retry wrapper function retry_wrapper = common.DecRetry(retry_exceptions=CONNECTION_EXCEPTIONS, tries=tries) # wrap the request function with the retry wrapper wrapped_func = retry_wrapper(self._make_request) # call the wrapped request function response = wrapped_func( verb, conductor_url, headers, params, data, raise_on_error=raise_on_error ) return response.text, response.status_code
make_request(self, uri_path='/', headers=None, params=None, data=None, verb=None, conductor_url=None, raise_on_error=True, tries=5, use_api_key=False)
- verb: PUT, POST, GET, DELETE, HEAD, PATCH