diff --git a/barbicanclient/client.py b/barbicanclient/client.py new file mode 100644 index 00000000..be0294a6 --- /dev/null +++ b/barbicanclient/client.py @@ -0,0 +1,218 @@ + +from eventlet.green.urllib import quote +import eventlet +eventlet.monkey_patch(socket=True, select=True) + +import json +import requests + +from barbicanclient.common.auth import authenticate +from barbicanclient.common.utils import proc_template +from barbicanclient.common.exceptions import ClientException +from urlparse import urljoin + + +class Connection(object): + def __init__(self, auth_endpoint, user, key, **kwargs): + """ + :param auth_endpoint: The auth URL to authenticate against + :param user: The user to authenticate as + :param key: The API key or passowrd to auth with + """ + self._auth_endpoint = auth_endpoint + self._user = user + self._key = key + self._endpoint = kwargs.get('endpoint') or 'https://barbican.api.rackspacecloud.com/v1/' + self._cacert = kwargs.get('cacert') + + # Hardcoded uri's right now + self.secrets_href = 'secrets/' + + + @property + def _conn(self): + """ + Property to enable decorators to work + properly + """ + return self + + @property + def auth_endpoint(self): + """The fully-qualified URI of the auth endpoint""" + return self._auth_endpoint + + @property + def endpoint(self): + """The fully-qualified URI of the endpoint""" + return self._endpoint + + def connect(self, token=None): + """ + Establishes a connection. If token is not None the + token will be used for this connection and auth will + not happen. + """ + self._session = requests.Session() + + #headers = {"Client-Id": self._client_id} + #self._session.headers.update(headers) + self._session.verify = True + + if token: + self.auth_token = token + else: + (self._endpoint, + self.auth_token) = authenticate(self._auth_endpoint, + self._user, self._key, + endpoint=self._endpoint, + cacert=self._cacert) + #self._load_homedoc_hrefs() + + @property + def auth_token(self): + try: + return self._session.headers['X-Auth-Token'] + except KeyError: + return None + + @auth_token.setter + def auth_token(self, value): + self._token = value + self._session.headers['X-Auth-Token'] = value + + def list_secrets(self): + """ + Returns the list of secrets for the auth'd tenant + """ + href = proc_template(self.secrets_href) + hdrs, body = self._perform_http(href=href, method='GET') + + #return Queue(self, href=href, name=queue_name, metadata=body) + + + + + + # + # def _load_homedoc_hrefs(self): + # """ + # Loads the home document hrefs for each endpoint + # Note: at the present time homedocs have not been + # implemented so these hrefs are simply hard-coded. When + # they are implemented we should update this function to + # actually parse the home document. + # """ + # + # # Queues endpoint{" + name + "}", quote(str(value))) + # self.queues_href = self._endpoint + "/queues" + # + # # Specific queue endpoint + # self.queue_href = self.queues_href + "/{queue_name}" + # + # # Messages endpoint + # self.messages_href = self.queue_href + "/messages" + # + # # Specific message endpoint + # self.message_href = self.messages_href + "/{message_id}" + # + # # Claims endpoint + # self._claims_href = self.queues_href + "/claims" + # + # # Specific claim endpoint + # self._claim_href = self.queues_href + "/claims/{claim_id}" + # + # # Actions endpoint + # self.actions_href = self._endpoint + "/actions" + # + # # Specific action endpoint + # self.action_href = self.actions_href + "/{action_id}" + # + # # Statistics endpoint + # self.stats_href = self.queue_href + "/stats" + # + # def create_queue(self, queue_name): + # """ + # Creates a queue with the specified name + # + # :param queue_name: The name of the queue + # :param ttl: The default time-to-live for messages in this queue + # """ + # href = proc_template(self.queue_href, queue_name=queue_name) + # body = {} + # + # self._perform_http(href=href, method='PUT', request_body=body) + # + # return Queue(self, href=href, name=queue_name, metadata=body) + # + # def get_queue(self, queue_name): + # """ + # Gets a queue by name + # + # :param queue_name: The name of the queue + # """ + # href = proc_template(self.queue_href, queue_name=queue_name) + # + # try: + # hdrs, body = self._perform_http(href=href, method='GET') + # except ClientException as ex: + # raise NoSuchQueueError(queue_name) if ex.http_status == 404 else ex + # + # return Queue(self, href=href, name=queue_name, metadata=body) + # + # def get_queues(self): + # href = self.queues_href + # + # hdrs, res = self._perform_http(href=href, method='GET') + # queues = res["queues"] + # + # for queue in queues: + # yield Queue(conn=self._conn, name=queue['name'], + # href=queue['href'], metadata=queue['metadata']) + # + # def delete_queue(self, queue_name): + # """ + # Deletes a queue + # + # :param queue_name: The name of the queue + # """ + # href = proc_template(self.queue_href, queue_name=queue_name) + # self._perform_http(href=href, method='DELETE') + # + # def get_queue_metadata(self, queue_name): + # href = proc_template(self._queue_href, queue_name=queue_name) + # + # try: + # return self._perform_http(conn, href, 'GET') + # except ClientException as ex: + # raise NoSuchQueueError(queue_name) if ex.http_status == 404 else ex + + def _perform_http(self, method, href, request_body='', headers={}): + """ + Perform an HTTP operation, checking for appropriate + errors, etc. and returns the response + + :param conn: The HTTPConnection or HTTPSConnection to use + :param method: The http method to use (GET, PUT, etc) + :param body: The optional body to submit + :param headers: Any additional headers to submit + :return: (headers, body) + """ + if not isinstance(request_body, str): + request_body = json.dumps(request_body) + + url = urljoin(self._endpoint, href) + + response = requests.request(method=method, url=url, data=request_body) + + #response = self._session.request(method=method, url=url, + # data=request_body, headers=headers) + + # Check if the status code is 2xx class + if not response.ok: + raise ClientException(href=href, method=method, http_status=response.status_code, + http_response_content=response.content) + + resp_body = json.loads(response.content) if response.content else '' + + return response.headers, resp_body \ No newline at end of file diff --git a/barbicanclient/common/auth.py b/barbicanclient/common/auth.py index 857e61b6..226c34fc 100644 --- a/barbicanclient/common/auth.py +++ b/barbicanclient/common/auth.py @@ -1,255 +1,73 @@ -from barbicanclient import exceptions + +from exceptions import ClientException + +from keystoneclient.v2_0 import client as ksclient +from keystoneclient import exceptions -def get_authenticator_cls(cls_or_name): - """Factory method to retrieve Authenticator class.""" - if isinstance(cls_or_name, type): - return cls_or_name - elif isinstance(cls_or_name, basestring): - if cls_or_name == "keystone": - return KeyStoneV2Authenticator - elif cls_or_name == "rax": - return RaxAuthenticator - elif cls_or_name == "auth1.1": - return Auth1_1 - elif cls_or_name == "fake": - return FakeAuth +def authenticate(auth_url, user, key, **kwargs): + """Authenticates against the endpoint to use. The correct + endpoint to use is looked up in the service catalog. The + caller can override this lookup by passing the endpoint + as a parameter. - raise ValueError("Could not determine authenticator class from the given " - "value %r." % cls_or_name) + :param auth_url: The keystone auth endpoint to use + :param user: The username to use for auth + :param key: The apikey to use for authentiation + :param endpoint: The Marconi endpoint to use. IOW, don't + look up an endpoint in the service catalog, just use + this one instead. + :param tenant_name: The optional tenant-name to use + :param tenant_id: The optional tenant ID toi use + :param cacert: The cacert PEM file to use + :param service_type: The service type to look for in + the service catalog + :param endpoint_type The endpoint type to reference in + the service catalog + :param region_name The region to pass for authentication + :returns: Tuple containing Marconi endpoint and token -class Authenticator(object): + :raises: ClientException """ - Helper class to perform Keystone or other miscellaneous authentication. + insecure = kwargs.get('insecure', False) + endpoint = kwargs.get('endpoint') + tenant_name = kwargs.get('tenant_name') + tenant_id = kwargs.get('tenant_id') + cacert = kwargs.get('cacert') - The "authenticate" method returns a ServiceCatalog, which can be used - to obtain a token. + try: + _ksclient = ksclient.Client(username=user, + password=key, + tenant_name=tenant_name, + tenant_id=tenant_id, + cacert=cacert, + auth_url=auth_url, + insecure=insecure) - """ + except exceptions.Unauthorized as ex: + raise ClientException('Unauthorized. Check username, password' + ' and tenant name/id') - URL_REQUIRED = True + except exceptions.AuthorizationFailure as err: + raise ClientException('Authorization Failure. %s' % err) - def __init__(self, client, type, url, username, password, tenant, - region=None, service_type=None, service_name=None, - service_url=None): - self.client = client - self.type = type - self.url = url - self.username = username - self.password = password - self.tenant = tenant - self.region = region - self.service_type = service_type - self.service_name = service_name - self.service_url = service_url + if not endpoint: + # The user did not pass in an endpoint, so we need to + # look one up on their behalf in the service catalog - def _authenticate(self, url, body, root_key='access'): - """Authenticate and extract the service catalog.""" - # Make sure we follow redirects when trying to reach Keystone - tmp_follow_all_redirects = self.client.follow_all_redirects - self.client.follow_all_redirects = True + # TODO(jdp): Ensure that this is the correct service_type field + service_type = kwargs.get('service_type', 'queueing') + endpoint_type = kwargs.get('endpoint_type', 'publicURL') + region = kwargs.get('region_name') try: - resp, body = self.client._time_request(url, "POST", body=body) - finally: - self.client.follow_all_redirects = tmp_follow_all_redirects + endpoint = _ksclient.service_catalog.url_for( + attr='region', + filter_value=region, + service_type=service_type, + endpoint_type=endpoint_type) + except exceptions.EndpointNotFound as ex: + raise ClientException('Endpoint not found in service catalog') - if resp.status == 200: # content must always present - try: - return ServiceCatalog(body, region=self.region, - service_type=self.service_type, - service_name=self.service_name, - service_url=self.service_url, - root_key=root_key) - except exceptions.AmbiguousEndpoints: - print "Found more than one valid endpoint. Use a more "\ - "restrictive filter" - raise - except KeyError: - raise exceptions.AuthorizationFailure() - except exceptions.EndpointNotFound: - print "Could not find any suitable endpoint. Correct region?" - raise - - elif resp.status == 305: - return resp['location'] - else: - raise exceptions.from_response(resp, body) - - def authenticate(self): - raise NotImplementedError("Missing authenticate method.") - - -class KeyStoneV2Authenticator(Authenticator): - - def authenticate(self): - if self.url is None: - raise exceptions.AuthUrlNotGiven() - return self._v2_auth(self.url) - - def _v2_auth(self, url): - """Authenticate against a v2.0 auth service.""" - body = {"auth": { - "passwordCredentials": { - "username": self.username, - "password": self.password} - } - } - - if self.tenant: - body['auth']['tenantName'] = self.tenant - - return self._authenticate(url, body) - - -class Auth1_1(Authenticator): - - def authenticate(self): - """Authenticate against a v2.0 auth service.""" - if self.url is None: - raise exceptions.AuthUrlNotGiven() - auth_url = self.url - body = {"credentials": {"username": self.username, - "key": self.password}} - return self._authenticate(auth_url, body, root_key='auth') - - try: - print(resp_body) - self.auth_token = resp_body['auth']['token']['id'] - except KeyError: - raise nova_exceptions.AuthorizationFailure() - - catalog = resp_body['auth']['serviceCatalog'] - if 'cloudDatabases' not in catalog: - raise nova_exceptions.EndpointNotFound() - endpoints = catalog['cloudDatabases'] - for endpoint in endpoints: - if self.region_name is None or \ - endpoint['region'] == self.region_name: - self.management_url = endpoint['publicURL'] - return - raise nova_exceptions.EndpointNotFound() - - -class RaxAuthenticator(Authenticator): - - def authenticate(self): - if self.url is None: - raise exceptions.AuthUrlNotGiven() - return self._rax_auth(self.url) - - def _rax_auth(self, url): - """Authenticate against the Rackspace auth service.""" - body = {'auth': { - 'RAX-KSKEY:apiKeyCredentials': { - 'username': self.username, - 'apiKey': self.password, - 'tenantName': self.tenant} - } - } - - return self._authenticate(self.url, body) - - -class FakeAuth(Authenticator): - """Useful for faking auth.""" - - def authenticate(self): - class FakeCatalog(object): - def __init__(self, auth): - self.auth = auth - - def get_public_url(self): - return "%s/%s" % ('http://localhost:8779/v1.0', - self.auth.tenant) - - def get_token(self): - return self.auth.tenant - - return FakeCatalog(self) - - -class ServiceCatalog(object): - """Represents a Keystone Service Catalog which describes a service. - - This class has methods to obtain a valid token as well as a public service - url and a management url. - - """ - - def __init__(self, resource_dict, region=None, service_type=None, - service_name=None, service_url=None, root_key='access'): - self.catalog = resource_dict - self.region = region - self.service_type = service_type - self.service_name = service_name - self.service_url = service_url - self.management_url = None - self.public_url = None - self.root_key = root_key - self._load() - - def _load(self): - if not self.service_url: - self.public_url = self._url_for(attr='region', - filter_value=self.region, - endpoint_type="publicURL") - self.management_url = self._url_for(attr='region', - filter_value=self.region, - endpoint_type="adminURL") - else: - self.public_url = self.service_url - self.management_url = self.service_url - - def get_token(self): - return self.catalog[self.root_key]['token']['id'] - - def get_management_url(self): - return self.management_url - - def get_public_url(self): - return self.public_url - - def _url_for(self, attr=None, filter_value=None, - endpoint_type='publicURL'): - """ - Fetch the public URL from the Reddwarf service for a particular - endpoint attribute. If none given, return the first. - """ - matching_endpoints = [] - if 'endpoints' in self.catalog: - # We have a bastardized service catalog. Treat it special. :/ - for endpoint in self.catalog['endpoints']: - if not filter_value or endpoint[attr] == filter_value: - matching_endpoints.append(endpoint) - if not matching_endpoints: - raise exceptions.EndpointNotFound() - - # We don't always get a service catalog back ... - if not 'serviceCatalog' in self.catalog[self.root_key]: - raise exceptions.EndpointNotFound() - - # Full catalog ... - catalog = self.catalog[self.root_key]['serviceCatalog'] - - for service in catalog: - if service.get("type") != self.service_type: - continue - - if (self.service_name and self.service_type == 'database' and - service.get('name') != self.service_name): - continue - - endpoints = service['endpoints'] - for endpoint in endpoints: - if not filter_value or endpoint.get(attr) == filter_value: - endpoint["serviceName"] = service.get("name") - matching_endpoints.append(endpoint) - - if not matching_endpoints: - raise exceptions.EndpointNotFound() - elif len(matching_endpoints) > 1: - raise exceptions.AmbiguousEndpoints(endpoints=matching_endpoints) - else: - return matching_endpoints[0].get(endpoint_type, None) \ No newline at end of file + return (endpoint, _ksclient.auth_token) \ No newline at end of file diff --git a/barbicanclient/common/exceptions.py b/barbicanclient/common/exceptions.py new file mode 100644 index 00000000..4d84dd45 --- /dev/null +++ b/barbicanclient/common/exceptions.py @@ -0,0 +1,12 @@ +class ClientException(Exception): + """Exception for wrapping up Marconi client errors""" + def __init__(self, href='', http_status=0, + method='', http_response_content=''): + + self.method = method + self.href = href + self.http_status = http_status + self.http_response_content = http_response_content + + msg = "%s %s returned %d" % (self.method, self.href, self.http_status) + Exception.__init__(self, msg) \ No newline at end of file diff --git a/barbicanclient/common/http.py b/barbicanclient/common/http.py deleted file mode 100644 index 16fdf418..00000000 --- a/barbicanclient/common/http.py +++ /dev/null @@ -1,229 +0,0 @@ -import httplib2 -import logging - -from barbicanclient.common import auth - - -class BarbicanHTTPClient(httplib2.Http): - - USER_AGENT = 'python-barbicanclient' - - def __init__(self, user, password, tenant, auth_url, service_name, - service_url=None, - auth_strategy=None, insecure=False, - timeout=None, proxy_tenant_id=None, - proxy_token=None, region_name=None, - endpoint_type='publicURL', service_type=None, - timings=False): - - super(BarbicanHTTPClient, self).__init__(timeout=timeout) - - self.username = user - self.password = password - self.tenant = tenant - if auth_url: - self.auth_url = auth_url.rstrip('/') - else: - self.auth_url = None - self.region_name = region_name - self.endpoint_type = endpoint_type - self.service_url = service_url - self.service_type = service_type - self.service_name = service_name - self.timings = timings - - self.times = [] # [("item", starttime, endtime), ...] - - self.auth_token = None - self.proxy_token = proxy_token - self.proxy_tenant_id = proxy_tenant_id - - # httplib2 overrides - self.force_exception_to_status_code = True - self.disable_ssl_certificate_validation = insecure - - auth_cls = auth.get_authenticator_cls(auth_strategy) - - self.authenticator = auth_cls(self, auth_strategy, - self.auth_url, self.username, - self.password, self.tenant, - region=region_name, - service_type=service_type, - service_name=service_name, - service_url=service_url) - - def get_timings(self): - return self.times - - def http_log(self, args, kwargs, resp, body): - if not RDC_PP: - self.simple_log(args, kwargs, resp, body) - else: - self.pretty_log(args, kwargs, resp, body) - - def simple_log(self, args, kwargs, resp, body): - if not _logger.isEnabledFor(logging.DEBUG): - return - - string_parts = ['curl -i'] - for element in args: - if element in ('GET', 'POST'): - string_parts.append(' -X %s' % element) - else: - string_parts.append(' %s' % element) - - for element in kwargs['headers']: - header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) - string_parts.append(header) - - _logger.debug("REQ: %s\n" % "".join(string_parts)) - if 'body' in kwargs: - _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) - _logger.debug("RESP:%s %s\n", resp, body) - - def pretty_log(self, args, kwargs, resp, body): - from reddwarfclient import common - if not _logger.isEnabledFor(logging.DEBUG): - return - - string_parts = ['curl -i'] - for element in args: - if element in ('GET', 'POST'): - string_parts.append(' -X %s' % element) - else: - string_parts.append(' %s' % element) - - for element in kwargs['headers']: - header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) - string_parts.append(header) - - curl_cmd = "".join(string_parts) - _logger.debug("REQUEST:") - if 'body' in kwargs: - _logger.debug("%s -d '%s'" % (curl_cmd, kwargs['body'])) - try: - req_body = json.dumps(json.loads(kwargs['body']), - sort_keys=True, indent=4) - except: - req_body = kwargs['body'] - _logger.debug("BODY: %s\n" % (req_body)) - else: - _logger.debug(curl_cmd) - - try: - resp_body = json.dumps(json.loads(body), sort_keys=True, indent=4) - except: - resp_body = body - _logger.debug("RESPONSE HEADERS: %s" % resp) - _logger.debug("RESPONSE BODY : %s" % resp_body) - - def request(self, *args, **kwargs): - kwargs.setdefault('headers', kwargs.get('headers', {})) - kwargs['headers']['User-Agent'] = self.USER_AGENT - self.morph_request(kwargs) - - resp, body = super(ReddwarfHTTPClient, self).request(*args, **kwargs) - - # Save this in case anyone wants it. - self.last_response = (resp, body) - self.http_log(args, kwargs, resp, body) - - if body: - try: - body = self.morph_response_body(body) - except exceptions.ResponseFormatError: - # Acceptable only if the response status is an error code. - # Otherwise its the API or client misbehaving. - self.raise_error_from_status(resp, None) - raise # Not accepted! - else: - body = None - - if resp.status in expected_errors: - raise exceptions.from_response(resp, body) - - return resp, body - - def raise_error_from_status(self, resp, body): - if resp.status in expected_errors: - raise exceptions.from_response(resp, body) - - def morph_request(self, kwargs): - kwargs['headers']['Accept'] = 'application/json' - kwargs['headers']['Content-Type'] = 'application/json' - if 'body' in kwargs: - kwargs['body'] = json.dumps(kwargs['body']) - - def morph_response_body(self, body_string): - try: - return json.loads(body_string) - except ValueError: - raise exceptions.ResponseFormatError() - - def _time_request(self, url, method, **kwargs): - start_time = time.time() - resp, body = self.request(url, method, **kwargs) - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - return resp, body - - def _cs_request(self, url, method, **kwargs): - def request(): - kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token - if self.tenant: - kwargs['headers']['X-Auth-Project-Id'] = self.tenant - - resp, body = self._time_request(self.service_url + url, method, - **kwargs) - return resp, body - - if not self.auth_token or not self.service_url: - self.authenticate() - - # 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: - return request() - except exceptions.Unauthorized, ex: - self.authenticate() - return request() - - def get(self, url, **kwargs): - return self._cs_request(url, 'GET', **kwargs) - - def post(self, url, **kwargs): - return self._cs_request(url, 'POST', **kwargs) - - def put(self, url, **kwargs): - return self._cs_request(url, 'PUT', **kwargs) - - def delete(self, url, **kwargs): - return self._cs_request(url, 'DELETE', **kwargs) - - def authenticate(self): - """Auths the client and gets a token. May optionally set a service url. - - The client will get auth errors until the authentication step - occurs. Additionally, if a service_url was not explicitly given in - the clients __init__ method, one will be obtained from the auth - service. - - """ - catalog = self.authenticator.authenticate() - if self.service_url: - possible_service_url = None - else: - if self.endpoint_type == "publicURL": - possible_service_url = catalog.get_public_url() - elif self.endpoint_type == "adminURL": - possible_service_url = catalog.get_management_url() - self.authenticate_with_token(catalog.get_token(), possible_service_url) - - def authenticate_with_token(self, token, service_url=None): - self.auth_token = token - if not self.service_url: - if not service_url: - raise exceptions.ServiceUrlNotGiven() - else: - self.service_url = service_url \ No newline at end of file diff --git a/barbicanclient/common/utils.py b/barbicanclient/common/utils.py new file mode 100644 index 00000000..843e04fe --- /dev/null +++ b/barbicanclient/common/utils.py @@ -0,0 +1,10 @@ +import urllib + + +def proc_template(template, **kwargs): + """ + Processes a templated URL by substituting the + dictionary args and returning the strings. + """ + return template.format(**dict([(k, urllib.quote(v)) + for k, v in kwargs.items()])) \ No newline at end of file diff --git a/barbicanclient/exceptions.py b/barbicanclient/exceptions.py deleted file mode 100644 index b6a8c36b..00000000 --- a/barbicanclient/exceptions.py +++ /dev/null @@ -1,164 +0,0 @@ -class UnsupportedVersion(Exception): - """Indicates that the user is trying to use an unsupported - version of the API""" - pass - - -class CommandError(Exception): - pass - - -class AuthorizationFailure(Exception): - pass - - -class NoUniqueMatch(Exception): - pass - - -class NoTokenLookupException(Exception): - """This form of authentication does not support looking up - endpoints from an existing token.""" - pass - - -class EndpointNotFound(Exception): - """Could not find Service or Region in Service Catalog.""" - pass - - -class AuthUrlNotGiven(EndpointNotFound): - """The auth url was not given.""" - pass - - -class ServiceUrlNotGiven(EndpointNotFound): - """The service url was not given.""" - pass - - -class ResponseFormatError(Exception): - """Could not parse the response format.""" - pass - - -class AmbiguousEndpoints(Exception): - """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): - self.endpoints = endpoints - - def __str__(self): - return "AmbiguousEndpoints: %s" % repr(self.endpoints) - - -class ClientException(Exception): - """ - The base exception class for all exceptions this library raises. - """ - def __init__(self, code, message=None, details=None, request_id=None): - self.code = code - self.message = message or self.__class__.message - self.details = details - self.request_id = request_id - - def __str__(self): - formatted_string = "%s (HTTP %s)" % (self.message, self.code) - if self.request_id: - formatted_string += " (Request-ID: %s)" % self.request_id - - return formatted_string - - -class BadRequest(ClientException): - """ - HTTP 400 - Bad request: you sent some malformed data. - """ - http_status = 400 - message = "Bad request" - - -class Unauthorized(ClientException): - """ - HTTP 401 - Unauthorized: bad credentials. - """ - http_status = 401 - message = "Unauthorized" - - -class Forbidden(ClientException): - """ - HTTP 403 - Forbidden: your credentials don't give you access to this - resource. - """ - http_status = 403 - message = "Forbidden" - - -class NotFound(ClientException): - """ - HTTP 404 - Not found - """ - http_status = 404 - message = "Not found" - - -class OverLimit(ClientException): - """ - HTTP 413 - Over limit: you're over the API limits for this time period. - """ - http_status = 413 - message = "Over limit" - - -# NotImplemented is a python keyword. -class HTTPNotImplemented(ClientException): - """ - HTTP 501 - Not Implemented: the server does not support this operation. - """ - http_status = 501 - message = "Not Implemented" - - -class UnprocessableEntity(ClientException): - """ - HTTP 422 - Unprocessable Entity: The request cannot be processed. - """ - http_status = 422 - message = "Unprocessable Entity" - - -# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() -# so we can do this: -# _code_map = dict((c.http_status, c) -# for c in ClientException.__subclasses__()) -# -# Instead, we have to hardcode it: -_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized, - Forbidden, NotFound, OverLimit, - HTTPNotImplemented, - UnprocessableEntity]) - - -def from_response(response, body): - """ - Return an instance of an ClientException or subclass - based on an httplib2 response. - - Usage:: - - resp, body = http.request(...) - if resp.status != 200: - raise exception_from_response(resp, body) - """ - cls = _code_map.get(response.status, ClientException) - if body: - message = "n/a" - details = "n/a" - if hasattr(body, 'keys'): - error = body[body.keys()[0]] - message = error.get('message', None) - details = error.get('details', None) - return cls(code=response.status, message=message, details=details) - else: - request_id = response.get('x-compute-request-id') - return cls(code=response.status, request_id=request_id) \ No newline at end of file diff --git a/barbicanclient/secrets.py b/barbicanclient/secrets.py new file mode 100644 index 00000000..7392aeaa --- /dev/null +++ b/barbicanclient/secrets.py @@ -0,0 +1,14 @@ +class Secret(object): + """ + A secret is any data the user has stored in the key management system. + """ + def __init__(self, connection, json): + """ + Builds a secret object from a json representation. Includes the connection object for subtasks. + """ + + + + + def __repr__(self): + return "" % self.name diff --git a/barbicanclient/v1/__init__.py b/examples/__init__.py similarity index 100% rename from barbicanclient/v1/__init__.py rename to examples/__init__.py diff --git a/examples/secrets.py b/examples/secrets.py new file mode 100644 index 00000000..e1b387e0 --- /dev/null +++ b/examples/secrets.py @@ -0,0 +1,31 @@ +import argparse + +from barbicanclient import client + +IDENTITY = 'https://identity.api.rackspacecloud.com/v2.0' +ENDPOINT = 'https://barbican.api.rackspacecloud.com/v1/' + + +def list_secrets(username, password): + connection = client.Connection(IDENTITY, username, password) + secrets = connection.list_secrets() + + print secrets.list() + + +def parse_args(): + parser = argparse.ArgumentParser(description='Testing code for barbican secrets api resource.') + parser.add_argument('--username', help='The keystone username used for for authentication') + parser.add_argument('--password', help='The keystone password used for for authentication') + parser.add_argument('--keystone', default=IDENTITY, + help='The keystone endpoint used for for authentication') + parser.add_argument('--endpoint', default=ENDPOINT, + help='The barbican endpoint to test against') + + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + list_secrets(args.username, args.password) \ No newline at end of file diff --git a/tests/test_auth.py b/tests/test_auth.py deleted file mode 100644 index 6de1191d..00000000 --- a/tests/test_auth.py +++ /dev/null @@ -1,407 +0,0 @@ -import contextlib - -from testtools import TestCase -from barbicanclient.common import auth -from mock import Mock - -from barbicanclient import exceptions - -#Unit tests for the classes and functions in auth.py. - - -def check_url_none(test_case, auth_class): - # url is None, it must throw exception - authObj = auth_class(url=None, type=auth_class, client=None, - username=None, password=None, tenant=None) - try: - authObj.authenticate() - test_case.fail("AuthUrlNotGiven exception expected") - except exceptions.AuthUrlNotGiven: - pass - - -class AuthenticatorTest(TestCase): - def setUp(self): - super(AuthenticatorTest, self).setUp() - self.orig_load = auth.ServiceCatalog._load - self.orig__init = auth.ServiceCatalog.__init__ - - def tearDown(self): - super(AuthenticatorTest, self).tearDown() - auth.ServiceCatalog._load = self.orig_load - auth.ServiceCatalog.__init__ = self.orig__init - - def test_get_authenticator_cls(self): - class_list = (auth.KeyStoneV2Authenticator, - auth.RaxAuthenticator, - auth.Auth1_1, - auth.FakeAuth) - - for c in class_list: - self.assertEqual(c, auth.get_authenticator_cls(c)) - - class_names = {"keystone": auth.KeyStoneV2Authenticator, - "rax": auth.RaxAuthenticator, - "auth1.1": auth.Auth1_1, - "fake": auth.FakeAuth} - - for cn in class_names.keys(): - self.assertEqual(class_names[cn], auth.get_authenticator_cls(cn)) - - cls_or_name = "_unknown_" - self.assertRaises(ValueError, auth.get_authenticator_cls, cls_or_name) - - def test__authenticate(self): - authObj = auth.Authenticator(Mock(), auth.KeyStoneV2Authenticator, - Mock(), Mock(), Mock(), Mock()) - # test response code 200 - resp = Mock() - resp.status = 200 - body = "test_body" - - auth.ServiceCatalog._load = Mock(return_value=1) - authObj.client._time_request = Mock(return_value=(resp, body)) - - sc = authObj._authenticate(Mock(), Mock()) - self.assertEqual(body, sc.catalog) - - # test AmbiguousEndpoints exception - auth.ServiceCatalog.__init__ = \ - Mock(side_effect=exceptions.AmbiguousEndpoints) - self.assertRaises(exceptions.AmbiguousEndpoints, - authObj._authenticate, Mock(), Mock()) - - # test handling KeyError and raising AuthorizationFailure exception - auth.ServiceCatalog.__init__ = Mock(side_effect=KeyError) - self.assertRaises(exceptions.AuthorizationFailure, - authObj._authenticate, Mock(), Mock()) - - # test EndpointNotFound exception - mock = Mock(side_effect=exceptions.EndpointNotFound) - auth.ServiceCatalog.__init__ = mock - self.assertRaises(exceptions.EndpointNotFound, - authObj._authenticate, Mock(), Mock()) - mock.side_effect = None - - # test response code 305 - resp.__getitem__ = Mock(return_value='loc') - resp.status = 305 - body = "test_body" - authObj.client._time_request = Mock(return_value=(resp, body)) - - l = authObj._authenticate(Mock(), Mock()) - self.assertEqual('loc', l) - - # test any response code other than 200 and 305 - resp.status = 404 - exceptions.from_response = Mock(side_effect=ValueError) - self.assertRaises(ValueError, authObj._authenticate, Mock(), Mock()) - - def test_authenticate(self): - authObj = auth.Authenticator(Mock(), auth.KeyStoneV2Authenticator, - Mock(), Mock(), Mock(), Mock()) - self.assertRaises(NotImplementedError, authObj.authenticate) - - -class KeyStoneV2AuthenticatorTest(TestCase): - def test_authenticate(self): - # url is None - check_url_none(self, auth.KeyStoneV2Authenticator) - - # url is not None, so it must not throw exception - url = "test_url" - cls_type = auth.KeyStoneV2Authenticator - authObj = auth.KeyStoneV2Authenticator(url=url, type=cls_type, - client=None, username=None, - password=None, tenant=None) - - def side_effect_func(url): - return url - - mock = Mock() - mock.side_effect = side_effect_func - authObj._v2_auth = mock - r = authObj.authenticate() - self.assertEqual(url, r) - - def test__v2_auth(self): - username = "reddwarf_user" - password = "reddwarf_password" - tenant = "tenant" - cls_type = auth.KeyStoneV2Authenticator - authObj = auth.KeyStoneV2Authenticator(url=None, type=cls_type, - client=None, - username=username, - password=password, - tenant=tenant) - - def side_effect_func(url, body): - return body - - mock = Mock() - mock.side_effect = side_effect_func - authObj._authenticate = mock - body = authObj._v2_auth(Mock()) - self.assertEqual(username, - body['auth']['passwordCredentials']['username']) - self.assertEqual(password, - body['auth']['passwordCredentials']['password']) - self.assertEqual(tenant, body['auth']['tenantName']) - - -class Auth1_1Test(TestCase): - def test_authenticate(self): - # handle when url is None - check_url_none(self, auth.Auth1_1) - - # url is not none - username = "reddwarf_user" - password = "reddwarf_password" - url = "test_url" - authObj = auth.Auth1_1(url=url, - type=auth.Auth1_1, - client=None, username=username, - password=password, tenant=None) - - def side_effect_func(auth_url, body, root_key): - return auth_url, body, root_key - - mock = Mock() - mock.side_effect = side_effect_func - authObj._authenticate = mock - auth_url, body, root_key = authObj.authenticate() - - self.assertEqual(username, body['credentials']['username']) - self.assertEqual(password, body['credentials']['key']) - self.assertEqual(auth_url, url) - self.assertEqual('auth', root_key) - - -class RaxAuthenticatorTest(TestCase): - def test_authenticate(self): - # url is None - check_url_none(self, auth.RaxAuthenticator) - - # url is not None, so it must not throw exception - url = "test_url" - authObj = auth.RaxAuthenticator(url=url, - type=auth.RaxAuthenticator, - client=None, username=None, - password=None, tenant=None) - - def side_effect_func(url): - return url - - mock = Mock() - mock.side_effect = side_effect_func - authObj._rax_auth = mock - r = authObj.authenticate() - self.assertEqual(url, r) - - def test__rax_auth(self): - username = "reddwarf_user" - password = "reddwarf_password" - tenant = "tenant" - authObj = auth.RaxAuthenticator(url=None, - type=auth.RaxAuthenticator, - client=None, username=username, - password=password, tenant=tenant) - - def side_effect_func(url, body): - return body - - mock = Mock() - mock.side_effect = side_effect_func - authObj._authenticate = mock - body = authObj._rax_auth(Mock()) - - v = body['auth']['RAX-KSKEY:apiKeyCredentials']['username'] - self.assertEqual(username, v) - - v = body['auth']['RAX-KSKEY:apiKeyCredentials']['apiKey'] - self.assertEqual(password, v) - - v = body['auth']['RAX-KSKEY:apiKeyCredentials']['tenantName'] - self.assertEqual(tenant, v) - - -class FakeAuthTest(TestCase): - def test_authenticate(self): - tenant = "tenant" - authObj = auth.FakeAuth(url=None, - type=auth.FakeAuth, - client=None, username=None, - password=None, tenant=tenant) - - fc = authObj.authenticate() - public_url = "%s/%s" % ('http://localhost:8779/v1.0', tenant) - self.assertEqual(public_url, fc.get_public_url()) - self.assertEqual(tenant, fc.get_token()) - - -class ServiceCatalogTest(TestCase): - def setUp(self): - super(ServiceCatalogTest, self).setUp() - self.orig_url_for = auth.ServiceCatalog._url_for - self.orig__init__ = auth.ServiceCatalog.__init__ - auth.ServiceCatalog.__init__ = Mock(return_value=None) - self.test_url = "http://localhost:1234/test" - - def tearDown(self): - super(ServiceCatalogTest, self).tearDown() - auth.ServiceCatalog._url_for = self.orig_url_for - auth.ServiceCatalog.__init__ = self.orig__init__ - - def test__load(self): - url = "random_url" - auth.ServiceCatalog._url_for = Mock(return_value=url) - - # when service_url is None - scObj = auth.ServiceCatalog() - scObj.region = None - scObj.service_url = None - scObj._load() - self.assertEqual(url, scObj.public_url) - self.assertEqual(url, scObj.management_url) - - # service url is not None - service_url = "service_url" - scObj = auth.ServiceCatalog() - scObj.region = None - scObj.service_url = service_url - scObj._load() - self.assertEqual(service_url, scObj.public_url) - self.assertEqual(service_url, scObj.management_url) - - def test_get_token(self): - test_id = "test_id" - scObj = auth.ServiceCatalog() - scObj.root_key = "root_key" - scObj.catalog = dict() - scObj.catalog[scObj.root_key] = dict() - scObj.catalog[scObj.root_key]['token'] = dict() - scObj.catalog[scObj.root_key]['token']['id'] = test_id - self.assertEqual(test_id, scObj.get_token()) - - def test_get_management_url(self): - test_mng_url = "test_management_url" - scObj = auth.ServiceCatalog() - scObj.management_url = test_mng_url - self.assertEqual(test_mng_url, scObj.get_management_url()) - - def test_get_public_url(self): - test_public_url = "test_public_url" - scObj = auth.ServiceCatalog() - scObj.public_url = test_public_url - self.assertEqual(test_public_url, scObj.get_public_url()) - - def test__url_for(self): - scObj = auth.ServiceCatalog() - - # case for no endpoint found - self.case_no_endpoint_match(scObj) - - # case for empty service catalog - self.case_endpoing_with_empty_catalog(scObj) - - # more than one matching endpoints - self.case_ambiguous_endpoint(scObj) - - # happy case - self.case_unique_endpoint(scObj) - - # testing if-statements in for-loop to iterate services in catalog - self.case_iterating_services_in_catalog(scObj) - - def case_no_endpoint_match(self, scObj): - # empty endpoint list - scObj.catalog = dict() - scObj.catalog['endpoints'] = list() - self.assertRaises(exceptions.EndpointNotFound, scObj._url_for) - - def side_effect_func_ep(attr): - return "test_attr_value" - - # simulating dict - endpoint = Mock() - mock = Mock() - mock.side_effect = side_effect_func_ep - endpoint.__getitem__ = mock - scObj.catalog['endpoints'].append(endpoint) - - # not-empty list but not matching endpoint - filter_value = "not_matching_value" - self.assertRaises(exceptions.EndpointNotFound, scObj._url_for, - attr="test_attr", filter_value=filter_value) - - filter_value = "test_attr_value" # so that we have an endpoint match - scObj.root_key = "access" - scObj.catalog[scObj.root_key] = dict() - self.assertRaises(exceptions.EndpointNotFound, scObj._url_for, - attr="test_attr", filter_value=filter_value) - - def case_endpoing_with_empty_catalog(self, scObj): - # first, test with empty catalog, this should pass since - # there is already enpoint added - scObj.catalog[scObj.root_key]['serviceCatalog'] = list() - - endpoint = scObj.catalog['endpoints'][0] - endpoint.get = Mock(return_value=self.test_url) - r_url = scObj._url_for(attr="test_attr", - filter_value="test_attr_value") - self.assertEqual(self.test_url, r_url) - - def case_ambiguous_endpoint(self, scObj): - scObj.service_type = "reddwarf" - scObj.service_name = "test_service_name" - - def side_effect_func_service(key): - if key == "type": - return "reddwarf" - elif key == "name": - return "test_service_name" - return None - - mock1 = Mock() - mock1.side_effect = side_effect_func_service - service1 = Mock() - service1.get = mock1 - - endpoint2 = {"test_attr": "test_attr_value"} - service1.__getitem__ = Mock(return_value=[endpoint2]) - scObj.catalog[scObj.root_key]['serviceCatalog'] = [service1] - self.assertRaises(exceptions.AmbiguousEndpoints, scObj._url_for, - attr="test_attr", filter_value="test_attr_value") - - def case_unique_endpoint(self, scObj): - # changing the endpoint2 attribute to pass the filter - service1 = scObj.catalog[scObj.root_key]['serviceCatalog'][0] - endpoint2 = service1[0][0] - endpoint2["test_attr"] = "new value not matching filter" - r_url = scObj._url_for(attr="test_attr", - filter_value="test_attr_value") - self.assertEqual(self.test_url, r_url) - - def case_iterating_services_in_catalog(self, scObj): - service1 = scObj.catalog[scObj.root_key]['serviceCatalog'][0] - - scObj.catalog = dict() - scObj.root_key = "access" - scObj.catalog[scObj.root_key] = dict() - scObj.service_type = "no_match" - - scObj.catalog[scObj.root_key]['serviceCatalog'] = [service1] - self.assertRaises(exceptions.EndpointNotFound, scObj._url_for) - - scObj.service_type = "database" - scObj.service_name = "no_match" - self.assertRaises(exceptions.EndpointNotFound, scObj._url_for) - - # no endpoints and no 'serviceCatalog' in catalog => raise exception - scObj = auth.ServiceCatalog() - scObj.catalog = dict() - scObj.root_key = "access" - scObj.catalog[scObj.root_key] = dict() - scObj.catalog[scObj.root_key]['serviceCatalog'] = [] - self.assertRaises(exceptions.EndpointNotFound, scObj._url_for, - attr="test_attr", filter_value="test_attr_value") \ No newline at end of file diff --git a/tools/pip-requires b/tools/pip-requires index b0ac31e7..7cf33e89 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,2 +1,4 @@ httplib2>=0.7.7 -argparse>=1.2.1 \ No newline at end of file +argparse>=1.2.1 +python-keystoneclient>=0.2.3 +eventlet>=0.12.1 \ No newline at end of file