Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
414 lines (353 sloc) 15.4 KB
import time
from datetime import datetime
import os
import sys
import dateutil.parser
import requests
try:
from urlparse import urlparse
except:
from urllib.parse import urlparse
try:
import json
except:
import simplejson as json
class IronTokenProvider(object):
def __init__(self, token):
self.token = token
def getToken(self):
return self.token
class KeystoneTokenProvider(object):
def __init__(self, keystone):
self.server = keystone["server"] + ("" if keystone["server"].endswith("/") else "/")
self.tenant = keystone["tenant"]
self.username = keystone["username"]
self.password = keystone["password"]
self.token = None
self.local_expires_at_timestamp = 0
def getToken(self):
date_diff = time.mktime(datetime.now().timetuple()) - self.local_expires_at_timestamp
if self.token is None or date_diff > -10:
payload = {
'auth': {
'tenantName': self.tenant,
'passwordCredentials': {
'username': self.username,
'password': self.password
}
}
}
headers = {'content-type': 'application/json', 'Accept': 'application/json'}
response = requests.post(self.server + 'tokens', data=json.dumps(payload), headers=headers)
response.raise_for_status()
result = response.json()
token_data = result['access']['token']
issued_at = dateutil.parser.parse(token_data['issued_at']).replace(tzinfo=None)
expires = dateutil.parser.parse(token_data['expires']).replace(tzinfo=None)
duration = expires - issued_at
self.local_expires_at_timestamp = time.mktime((datetime.now() + duration).timetuple())
self.token = token_data['id']
return self.token
class IronClient(object):
__version__ = "1.2.0"
def __init__(self, name, version, product, host=None, project_id=None,
token=None, protocol=None, port=None, api_version=None,
config_file=None, keystone=None, cloud=None, path_prefix=''):
"""Prepare a Client that can make HTTP calls and return it.
Keyword arguments:
name -- the name of the client. Required.
version -- the version of the client. Required.
product -- the name of the product the client will access. Required.
host -- the default domain the client will be requesting. Defaults
to None.
project_id -- the project ID the client will be requesting. Can be
found on http://hud.iron.io. Defaults to None.
token -- an API token found on http://hud.iron.io. Defaults to None.
protocol -- The default protocol the client will use for its requests.
Defaults to None.
port -- The default port the client will use for its requests. Defaults
to None.
api_version -- The version of the API the client will use for its
requests. Defaults to None.
config_file -- The config file to load configuration from. Defaults to
None.
"""
config = {
"host": None,
"protocol": "https",
"port": 443,
"api_version": None,
"project_id": None,
"token": None,
"keystone": None,
"path_prefix": None,
"cloud": None,
}
products = {
"iron_worker": {
"host": "worker-aws-us-east-1.iron.io",
"version": 2
},
"iron_mq": {
"host": "mq-aws-us-east-1-1.iron.io",
"version": 3
},
"iron_cache": {
"host": "cache-aws-us-east-1.iron.io",
"version": 1
}
}
if product in products:
config["host"] = products[product]["host"]
config["api_version"] = products[product]["version"]
config = configFromFile(config,
os.path.expanduser("~/.iron.json"), product)
config = configFromEnv(config)
config = configFromEnv(config, product)
config = configFromFile(config, "iron.json", product)
config = configFromFile(config, config_file, product)
config = configFromArgs(config, host=host, project_id=project_id,
token=token, protocol=protocol, port=port,
api_version=api_version, keystone=keystone, cloud=cloud, path_prefix=path_prefix)
required_fields = ["project_id"]
for field in required_fields:
if config[field] is None:
raise ValueError("No %s set. %s is a required field." % (field, field))
keystone_configured = False
if config["keystone"] is not None:
keystone_required_keys = ["server", "tenant", "username", "password"]
if len(intersect(keystone_required_keys, config["keystone"].keys())) == len(keystone_required_keys):
self.token_provider = KeystoneTokenProvider(config["keystone"])
keystone_configured = True
else:
raise ValueError("Missing keystone keys.")
elif config["token"] is not None:
self.token_provider = IronTokenProvider(config["token"])
if config["token"] is None and not keystone_configured:
raise ValueError("At least one of token or keystone should be specified.")
self.name = name
self.version = version
self.product = product
self.host = config["host"]
self.project_id = config["project_id"]
self.token = config["token"]
self.keystone = config["keystone"]
self.protocol = config["protocol"]
self.port = config["port"]
self.api_version = config["api_version"]
self.cloud = config["cloud"]
self.headers = {
"Accept": "application/json",
"User-Agent": "%s (version: %s)" % (self.name, self.version)
}
self.path_prefix = config["path_prefix"]
if self.cloud is not None:
url = urlparse(self.cloud)
self.protocol = url.scheme
self.host = url.netloc.split(":")[0]
if url.port:
self.port = url.port
self.path_prefix = url.path.rstrip("/")
if self.protocol == "https" and self.port == 443:
self.base_url = "%s://%s%s/%s/" % (self.protocol, self.host, self.path_prefix, self.api_version)
else:
self.base_url = "%s://%s:%s%s/%s/" % (self.protocol, self.host,
self.port, self.path_prefix, self.api_version)
if self.project_id:
self.base_url += "projects/%s/" % self.project_id
def _doRequest(self, url, method, body="", headers={}):
if self.token or self.keystone:
headers["Authorization"] = "OAuth %s" % self.token_provider.getToken()
if method == "GET":
r = requests.get(url, headers=headers)
elif method == "POST":
r = requests.post(url, data=body, headers=headers)
elif method == "PUT":
r = requests.put(url, data=body, headers=headers)
elif method == "DELETE":
r = requests.delete(url, data=body, headers=headers)
elif method == "PATCH":
r = requests.patch(url, data=body, headers=headers)
else:
raise ValueError("Invalid HTTP method")
return r
def request(self, url, method, body="", headers={}, retry=True):
"""Execute an HTTP request and return a dict containing the response
and the response status code.
Keyword arguments:
url -- The path to execute the result against, not including the API
version or project ID, with no leading /. Required.
method -- The HTTP method to use. Required.
body -- A string or file object to send as the body of the request.
Defaults to an empty string.
headers -- HTTP Headers to send with the request. Can overwrite the
defaults. Defaults to {}.
retry -- Whether exponential backoff should be employed. Defaults
to True.
"""
if headers:
headers = dict(list(headers.items()) + list(self.headers.items()))
else:
headers = self.headers
if not sys.version_info >= (3,) and headers:
headers = dict((k.encode('ascii') if isinstance(k, unicode) else k,
v.encode('ascii') if isinstance(v, unicode) else v)
for k, v in headers.items())
url = self.base_url + url
if not sys.version_info >= (3,):
if isinstance(url, unicode):
url = url.encode('ascii')
r = self._doRequest(url, method, body, headers)
retry_http_codes = [503, 504]
if r.status_code in retry_http_codes and retry:
tries = 5
delay = .5
backoff = 2
while r.status_code in retry_http_codes and tries > 0:
tries -= 1
time.sleep(delay)
delay *= backoff
r = self._doRequest(url, method, body, headers)
r.raise_for_status()
result = {}
contentType = r.headers["Content-Type"]
if contentType is None:
contentType = "text/plain"
else:
contentType = contentType.split(";")[0]
if contentType.lower() == "application/json":
try:
result["body"] = json.loads(r.text)
except:
result["body"] = r.text
else:
result["body"] = r.text
result["status"] = r.status_code
result["resp"] = r
result["content-type"] = contentType
return result
def get(self, url, headers={}, retry=True):
"""Execute an HTTP GET request and return a dict containing the
response and the response status code.
Keyword arguments:
url -- The path to execute the result against, not including the API
version or project ID, with no leading /. Required.
headers -- HTTP Headers to send with the request. Can overwrite the
defaults. Defaults to {}.
retry -- Whether exponential backoff should be employed. Defaults
to True.
"""
return self.request(url=url, method="GET", headers=headers,
retry=retry)
def post(self, url, body="", headers={}, retry=True):
"""Execute an HTTP POST request and return a dict containing the
response and the response status code.
Keyword arguments:
url -- The path to execute the result against, not including the API
version or project ID, with no leading /. Required.
body -- A string or file object to send as the body of the request.
Defaults to an empty string.
headers -- HTTP Headers to send with the request. Can overwrite the
defaults. Defaults to {}.
retry -- Whether exponential backoff should be employed. Defaults
to True.
"""
headers["Content-Length"] = str(len(body))
return self.request(url=url, method="POST", body=body, headers=headers,
retry=retry)
def delete(self, url, headers={}, retry=True, body=""):
"""Execute an HTTP DELETE request and return a dict containing the
response and the response status code.
Keyword arguments:
url -- The path to execute the result against, not including the API
version or project ID, with no leading /. Required.
headers -- HTTP Headers to send with the request. Can overwrite the
defaults. Defaults to an empty dict.
retry -- Whether exponential backoff should be employed. Defaults
to True.
body -- A string or file object to send as the body of the request.
Defaults to an empty string.
"""
return self.request(url=url, method="DELETE", headers=headers,
retry=retry, body=body)
def put(self, url, body="", headers={}, retry=True):
"""Execute an HTTP PUT request and return a dict containing the
response and the response status code.
Keyword arguments:
url -- The path to execute the result against, not including the API
version or project ID, with no leading /. Required.
body -- A string or file object to send as the body of the request.
Defaults to an empty string.
headers -- HTTP Headers to send with the request. Can overwrite the
defaults. Defaults to {}.
retry -- Whether exponential backoff should be employed. Defaults
to True.
"""
return self.request(url=url, method="PUT", body=body, headers=headers,
retry=retry)
def patch(self, url, body="", headers={}, retry=True):
"""Execute an HTTP PATCH request and return a dict containing the
response and the response status code.
Keyword arguments:
url -- The path to execute the result against, not including the API
version or project ID, with no leading /. Required.
body -- A string or file object to send as the body of the request.
Defaults to an empty string.
headers -- HTTP Headers to send with the request. Can overwrite the
defaults. Defaults to {}.
retry -- Whether exponential backoff should be employed. Defaults
to True.
"""
return self.request(url=url, method="PATCH", body=body, headers=headers,
retry=retry)
@staticmethod
def fromRfc3339(timestamp=None):
if timestamp is None:
timestamp = datetime.now()
return timestamp
return dateutil.parser.parse(timestamp)
@staticmethod
def toRfc3339(timestamp=None):
if timestamp is None:
timestamp = datetime.now()
return timestamp.isoformat()
@staticmethod
def fromTimestamp(timestamp=None):
if timestamp is None:
timestamp = time.now()
return timestamp
return datetime.fromtimestamp(float(timestamp))
def configFromFile(config, path, product=None):
if path is None:
return config
if not os.path.exists(path):
return config
try:
file = open(path, "r")
except IOError:
return config
raw = json.loads(file.read())
file.close()
for k in raw.keys():
if k in config:
config[k] = raw[k]
if product is not None:
if product in raw:
for k in raw[product].keys():
config[k] = raw[product][k]
return config
def configFromEnv(config, product=None):
if product is None:
product = "iron"
for k in config.keys():
key = "%s_%s" % (product, k)
if key.upper() in os.environ:
config[k] = os.environ[key.upper()]
return config
def configFromArgs(config, **kwargs):
for k in kwargs:
if kwargs[k] is not None:
config[k] = kwargs[k]
return config
def intersect(a, b):
return list(set(a) & set(b))
Jump to Line
Something went wrong with that request. Please try again.