diff options
Diffstat (limited to 'keystoneclient')
| -rw-r--r-- | keystoneclient/generic/__init__.py | 0 | ||||
| -rw-r--r-- | keystoneclient/generic/client.py | 205 | ||||
| -rw-r--r-- | keystoneclient/generic/shell.py | 59 | ||||
| -rw-r--r-- | keystoneclient/shell.py | 47 | ||||
| -rw-r--r-- | keystoneclient/utils.py | 21 |
5 files changed, 312 insertions, 20 deletions
diff --git a/keystoneclient/generic/__init__.py b/keystoneclient/generic/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/keystoneclient/generic/__init__.py diff --git a/keystoneclient/generic/client.py b/keystoneclient/generic/client.py new file mode 100644 index 0000000..724c05b --- /dev/null +++ b/keystoneclient/generic/client.py @@ -0,0 +1,205 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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. + +import logging +import urlparse + +from keystoneclient import client +from keystoneclient import exceptions + +_logger = logging.getLogger(__name__) + + +class Client(client.HTTPClient): + """Client for the OpenStack Keystone pre-version calls API. + + :param string endpoint: A user-supplied endpoint URL for the keystone + service. + :param integer timeout: Allows customization of the timeout for client + http requests. (optional) + + Example:: + + >>> from keystoneclient.generic import client + >>> root = client.Client(auth_url=KEYSTONE_URL) + >>> versions = root.discover() + ... + >>> from keystoneclient.v2_0 import client as v2client + >>> keystone = v2client.Client(auth_url=versions['v2.0']['url']) + ... + >>> user = keystone.users.get(USER_ID) + >>> user.delete() + + """ + + def __init__(self, endpoint=None, **kwargs): + """ Initialize a new client for the Keystone v2.0 API. """ + super(Client, self).__init__(endpoint=endpoint, **kwargs) + self.endpoint = endpoint + + def discover(self, url=None): + """ Discover Keystone servers and return API versions supported. + + :param url: optional url to test (without version) + + Returns:: + + { + 'message': 'Keystone found at http://127.0.0.1:5000/', + 'v2.0': { + 'status': 'beta', + 'url': 'http://127.0.0.1:5000/v2.0/', + 'id': 'v2.0' + }, + } + + """ + if url: + return self._check_keystone_versions(url) + else: + return self._local_keystone_exists() + + def _local_keystone_exists(self): + """ Checks if Keystone is available on default local port 35357 """ + return self._check_keystone_versions("http://localhost:35357") + + def _check_keystone_versions(self, url): + """ Calls Keystone URL and detects the available API versions """ + try: + httpclient = client.HTTPClient() + resp, body = httpclient.request(url, "GET", + headers={'Accept': 'application/json'}) + if resp.status in (200, 204): # in some cases we get No Content + try: + results = {} + if 'version' in body: + results['message'] = "Keystone found at %s" % url + version = body['version'] + # Stable/diablo incorrect format + id, status, version_url = self._get_version_info( + version, url) + results[str(id)] = {"id": id, + "status": status, + "url": version_url} + return results + elif 'versions' in body: + # Correct format + results['message'] = "Keystone found at %s" % url + for version in body['versions']['values']: + id, status, version_url = self._get_version_info( + version, url) + results[str(id)] = {"id": id, + "status": status, + "url": version_url} + return results + else: + results['message'] = "Unrecognized response from %s" \ + % url + return results + except KeyError: + raise exceptions.AuthorizationFailure() + elif resp.status == 305: + return self._check_keystone_versions(resp['location']) + else: + raise exceptions.from_response(resp, body) + except Exception as e: + _logger.exception(e) + + def discover_extensions(self, url=None): + """ Discover Keystone extensions supported. + + :param url: optional url to test (should have a version in it) + + Returns:: + + { + 'message': 'Keystone extensions at http://127.0.0.1:35357/v2', + 'OS-KSEC2': 'OpenStack EC2 Credentials Extension', + } + + """ + if url: + return self._check_keystone_extensions(url) + + def _check_keystone_extensions(self, url): + """ Calls Keystone URL and detects the available extensions """ + try: + httpclient = client.HTTPClient() + if not url.endswith("/"): + url += '/' + resp, body = httpclient.request("%sextensions" % url, "GET", + headers={'Accept': 'application/json'}) + if resp.status in (200, 204): # in some cases we get No Content + try: + results = {} + if 'extensions' in body: + if 'values' in body['extensions']: + # Parse correct format (per contract) + for extension in body['extensions']['values']: + alias, name = self._get_extension_info( + extension['extension']) + results[alias] = name + return results + else: + # Support incorrect, but prevalent format + for extension in body['extensions']: + alias, name = self._get_extension_info( + extension) + results[alias] = name + return results + else: + results['message'] = "Unrecognized extensions" \ + " response from %s" % url + return results + except KeyError: + raise exceptions.AuthorizationFailure() + elif resp.status == 305: + return self._check_keystone_extensions(resp['location']) + else: + raise exceptions.from_response(resp, body) + except Exception as e: + _logger.exception(e) + + @staticmethod + def _get_version_info(version, root_url): + """ Parses version information + + :param version: a dict of a Keystone version response + :param root_url: string url used to construct + the version if no URL is provided. + :returns: tuple - (verionId, versionStatus, versionUrl) + """ + id = version['id'] + status = version['status'] + ref = urlparse.urljoin(root_url, id) + if 'links' in version: + for link in version['links']: + if link['rel'] == 'self': + ref = link['href'] + break + return (id, status, ref) + + @staticmethod + def _get_extension_info(extension): + """ Parses extension information + + :param extension: a dict of a Keystone extension response + :returns: tuple - (alias, name) + """ + alias = extension['alias'] + name = extension['name'] + return (alias, name) diff --git a/keystoneclient/generic/shell.py b/keystoneclient/generic/shell.py new file mode 100644 index 0000000..52d7d1f --- /dev/null +++ b/keystoneclient/generic/shell.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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. + +from keystoneclient import utils +from keystoneclient.generic import client + +CLIENT_CLASS = client.Client + + +@utils.unauthenticated +def do_discover(cs, args): + """ + Discover Keystone servers and show authentication protocols and + extensions supported. + + Usage:: + $ keystone discover + Keystone found at http://localhost:35357 + - supports version v1.0 (DEPRECATED) here http://localhost:35357/v1.0 + - supports version v1.1 (CURRENT) here http://localhost:35357/v1.1 + - supports version v2.0 (BETA) here http://localhost:35357/v2.0 + - and RAX-KSKEY: Rackspace API Key Authentication Admin Extension + - and RAX-KSGRP: Rackspace Keystone Group Extensions + """ + if cs.endpoint: + versions = cs.discover(cs.endpoint) + elif cs.auth_url: + versions = cs.discover(cs.auth_url) + else: + versions = cs.discover() + if versions: + if 'message' in versions: + print versions['message'] + for key, version in versions.iteritems(): + if key != 'message': + print " - supports version %s (%s) here %s" % \ + (version['id'], version['status'], version['url']) + extensions = cs.discover_extensions(version['url']) + if extensions: + for key, extension in extensions.iteritems(): + if key != 'message': + print " - and %s: %s" % \ + (key, extension) + else: + print "No Keystone-compatible endpoint found" diff --git a/keystoneclient/shell.py b/keystoneclient/shell.py index 01f7fc5..ba30054 100644 --- a/keystoneclient/shell.py +++ b/keystoneclient/shell.py @@ -26,6 +26,7 @@ import sys from keystoneclient import exceptions as exc from keystoneclient import utils from keystoneclient.v2_0 import shell as shell_v2_0 +from keystoneclient.generic import shell as shell_generic def env(e): @@ -99,6 +100,7 @@ class OpenStackIdentityShell(object): actions_module = shell_v2_0 self._find_actions(subparsers, actions_module) + self._find_actions(subparsers, shell_generic) self._find_actions(subparsers, self) return parser @@ -151,28 +153,33 @@ class OpenStackIdentityShell(object): #FIXME(usrleon): Here should be restrict for project id same as # for username or apikey but for compatibility it is not. - if not args.os_username: - raise exc.CommandError("You must provide a username:" - "via --username or env[OS_USERNAME]") - if not args.os_password: - raise exc.CommandError("You must provide a password, either" - "via --password or env[OS_PASSWORD]") - - if not args.os_auth_url: - raise exc.CommandError("You must provide a auth url, either" - "via --os-auth_url or via" - "env[OS_AUTH_URL]") - - self.cs = self.get_api_class(options.os_version)( - username=args.os_username, - tenant_name=args.os_tenant_name, - tenant_id=args.os_tenant_id, - password=args.os_password, - auth_url=args.os_auth_url, - region_name=args.os_region_name) + if not utils.isunauthenticated(args.func): + if not args.os_username: + raise exc.CommandError("You must provide a username:" + "via --username or env[OS_USERNAME]") + if not args.os_password: + raise exc.CommandError("You must provide a password, either" + "via --password or env[OS_PASSWORD]") + + if not args.os_auth_url: + raise exc.CommandError("You must provide a auth url, either" + "via --os-auth_url or via" + "env[OS_AUTH_URL]") + + if utils.isunauthenticated(args.func): + self.cs = shell_generic.CLIENT_CLASS(endpoint=args.os_auth_url) + else: + self.cs = self.get_api_class(options.version)( + username=args.os_username, + tenant_name=args.os_tenant_name, + tenant_id=args.os_tenant_id, + password=args.os_password, + auth_url=args.os_auth_url, + region_name=args.os_region_name) try: - self.cs.authenticate() + if not utils.isunauthenticated(args.func): + self.cs.authenticate() except exc.Unauthorized: raise exc.CommandError("Invalid OpenStack Keystone credentials.") except exc.AuthorizationFailure: diff --git a/keystoneclient/utils.py b/keystoneclient/utils.py index 75a9e08..2cb385a 100644 --- a/keystoneclient/utils.py +++ b/keystoneclient/utils.py @@ -67,3 +67,24 @@ def find_resource(manager, name_or_id): msg = "No %s with a name or ID of '%s' exists." % \ (manager.resource_class.__name__.lower(), name_or_id) raise exceptions.CommandError(msg) + + +def unauthenticated(f): + """ Adds 'unauthenticated' attribute to decorated function. + + Usage: + @unauthenticated + def mymethod(f): + ... + """ + f.unauthenticated = True + return f + + +def isunauthenticated(f): + """ + Checks to see if the function is marked as not requiring authentication + with the @unauthenticated decorator. Returns True if decorator is + set to True, False otherwise. + """ + return getattr(f, 'unauthenticated', False) |
