From 49284dc5cdbaeb70ebc5c56b9cdefbc40df9460d Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 17 Nov 2011 05:12:46 -0600 Subject: [PATCH] Add 'discover' command for Keystone discovery and version listing Added @unauthenticated decorator to mark subcommands that do not need authentication. And checks to skip authentication for these commands. Added novaclient.keystone to setup.py Change-Id: Id2fd60af305c30a950bdbae8f897192bfae4d797 --- novaclient/keystone/shell.py | 99 ++++++++++++++++++++++++++++++++++++ novaclient/shell.py | 53 ++++++++++++------- novaclient/utils.py | 21 ++++++++ setup.py | 3 +- 4 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 novaclient/keystone/shell.py diff --git a/novaclient/keystone/shell.py b/novaclient/keystone/shell.py new file mode 100644 index 000000000..18299cd61 --- /dev/null +++ b/novaclient/keystone/shell.py @@ -0,0 +1,99 @@ +# Copyright 2010 Jacob Kaplan-Moss + +# Copyright 2011 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 httplib2 +import urllib +import urlparse + +try: + import json +except ImportError: + import simplejson as json + +# Python 2.5 compat fix +if not hasattr(urlparse, 'parse_qsl'): + import cgi + urlparse.parse_qsl = cgi.parse_qsl + + +from novaclient import exceptions +from novaclient import utils +from novaclient import client + + +@utils.unauthenticated +def do_discover(cs, args): + """ + Discover Keystone servers and show authentication protocols supported. + + Usage: + $ nova discover + Keystone found at http://localhost:35357 + - supports version v2.0 (beta) here http://localhost:35357/v2.0 + Keystone found at https://openstack.org/ + - supports version v1.0 (DEPRECATED) here https://openstack.org/v1.0 + - supports version v1.1 (CURRENT) here https://openstack.org/v1.1 + - supports version v2.0 (BETA) here https://openstack.org/v2.0 + """ + _local_keystone_exists() + _check_keystone_versions(cs.client.auth_url) + + +def _local_keystone_exists(): + return _check_keystone_versions("http://localhost:35357") + + +def _check_keystone_versions(url): + try: + httpclient = client.HTTPClient(user=None, password=None, + projectid=None, auth_url=None) + resp, body = httpclient.request(url, "GET", + headers={'Accept': 'application/json'}) + if resp.status in (200, 204): # in some cases we get No Content + try: + print "Keystone found at %s" % url + if 'version' in body: + version = body['version'] + # Stable/diablo incorrect format + _display_version_info(version, url) + return True + if 'versions' in body: + # Correct format + for version in body['versions']['values']: + _display_version_info(version, url) + return True + print "Unrecognized response from %s" % url + except KeyError: + raise exceptions.AuthorizationFailure() + elif resp.status == 305: + return _check_keystone_versions(resp['location']) + else: + raise exceptions.from_response(resp, body) + except: + return False + + +def _display_version_info(version, url): + id = version['id'] + status = version['status'] + ref = urlparse.urljoin(url, id) + if 'links' in version: + for link in version['links']: + if link['rel'] == 'self': + ref = link['href'] + break + print " - supports version %s (%s) here %s" % (id, status, ref) diff --git a/novaclient/shell.py b/novaclient/shell.py index 989b6ef19..b46a174d9 100644 --- a/novaclient/shell.py +++ b/novaclient/shell.py @@ -29,6 +29,7 @@ from novaclient import base from novaclient import exceptions as exc from novaclient import utils from novaclient.v1_1 import shell as shell_v1_1 +from novaclient.keystone import shell as shell_keystone def env(*vars): @@ -119,6 +120,7 @@ class OpenStackComputeShell(object): actions_module = shell_v1_1 self._find_actions(subparsers, actions_module) + self._find_actions(subparsers, shell_keystone) self._find_actions(subparsers, self) for _, _, ext_module in extensions: @@ -229,27 +231,39 @@ class OpenStackComputeShell(object): #FIXME(usrleon): Here should be restrict for project id same as # for username or password but for compatibility it is not. - if not user: - raise exc.CommandError("You must provide a username, either " - "via --username or via " - "env[OS_USER_NAME]") + if not utils.isunauthenticated(args.func): + if not user: + raise exc.CommandError("You must provide a username, either " + "via --username or via " + "env[NOVA_USERNAME]") - if not password: - if not apikey: - raise exc.CommandError("You must provide a password, either " - "via --password or via env[OS_PASSWORD]") - else: - password = apikey + if not password: + if not apikey: + raise exc.CommandError("You must provide a password, " + "either via --password or via env[NOVA_PASSWORD]") + else: + password = apikey - if not projectid: - raise exc.CommandError("You must provide an projectid, either " - "via --projectid or via " - "env[OS_TENANT_NAME]") + if not projectid: + raise exc.CommandError("You must provide an projectid, either " + "via --projectid or via " + "env[OS_TENANT_NAME]") - if not url: - raise exc.CommandError("You must provide a auth url, either " - "via --url or via " - "env[OS_AUTH_URL]") + if not url: + raise exc.CommandError("You must provide a auth url, either " + "via --url or via " + "env[OS_AUTH_URL]") + + if options.version and options.version != '1.0': + if not projectid: + raise exc.CommandError("You must provide an projectid, " + "either via --projectid or via " + "env[NOVA_PROJECT_ID") + + if not url: + raise exc.CommandError("You must provide a auth url," + " either via --url or via " + "env[NOVA_URL") self.cs = self.get_api_class(options.version)(user, password, projectid, url, insecure, @@ -258,7 +272,8 @@ class OpenStackComputeShell(object): extensions=extensions) try: - self.cs.authenticate() + if not utils.isunauthenticated(args.func): + self.cs.authenticate() except exc.Unauthorized: raise exc.CommandError("Invalid OpenStack Nova credentials.") except exc.AuthorizationFailure: diff --git a/novaclient/utils.py b/novaclient/utils.py index 083620add..9c6dbd079 100644 --- a/novaclient/utils.py +++ b/novaclient/utils.py @@ -15,6 +15,27 @@ def arg(*args, **kwargs): return _decorator +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) + + def pretty_choice_list(l): return ', '.join("'%s'" % i for i in l) diff --git a/setup.py b/setup.py index c209667d4..701295c33 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,8 @@ setuptools.setup( long_description=read_file("README.rst"), license="Apache License, Version 2.0", url="https://github.com/openstack/python-novaclient", - packages=["novaclient", "novaclient.v1_1", "novaclient.v1_1.contrib"], + packages=["novaclient", "novaclient.v1_1", "novaclient.v1_1.contrib", + "novaclient.keystone"], install_requires=requirements, tests_require=["nose", "mock"], test_suite="nose.collector",