diff options
Diffstat (limited to 'openstackclient')
29 files changed, 372 insertions, 320 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 2bd5271f..f6e99cdc 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -18,6 +18,8 @@ import logging import stevedore +from oslo.config import cfg + from keystoneclient.auth import base from openstackclient.common import exceptions as exc @@ -53,14 +55,14 @@ for plugin in PLUGIN_LIST: ) -def _guess_authentication_method(options): +def select_auth_plugin(options): """If no auth plugin was specified, pick one based on other options""" - if options.os_url: - # service token authentication, do nothing - return auth_plugin = None - if options.os_password: + if options.os_url and options.os_token: + # service token authentication + auth_plugin = 'token_endpoint' + elif options.os_username: if options.os_identity_api_version == '3': auth_plugin = 'v3password' elif options.os_identity_api_version == '2.0': @@ -83,14 +85,13 @@ def _guess_authentication_method(options): ) LOG.debug("No auth plugin selected, picking %s from other " "options" % auth_plugin) - options.os_auth_plugin = auth_plugin + return auth_plugin def build_auth_params(cmd_options): auth_params = {} - if cmd_options.os_url: - return {'token': cmd_options.os_token} if cmd_options.os_auth_plugin: + LOG.debug('auth_plugin: %s', cmd_options.os_auth_plugin) auth_plugin = base.get_plugin_class(cmd_options.os_auth_plugin) plugin_options = auth_plugin.get_options() for option in plugin_options: @@ -110,6 +111,7 @@ def build_auth_params(cmd_options): None, ) else: + LOG.debug('no auth_plugin') # delay the plugin choice, grab every option plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST]) for option in plugin_options: @@ -178,3 +180,54 @@ def build_auth_plugins_option_parser(parser): help=argparse.SUPPRESS, ) return parser + + +class TokenEndpoint(base.BaseAuthPlugin): + """Auth plugin to handle traditional token/endpoint usage + + Implements the methods required to handle token authentication + with a user-specified token and service endpoint; no Identity calls + are made for re-scoping, service catalog lookups or the like. + + The purpose of this plugin is to get rid of the special-case paths + in the code to handle this authentication format. Its primary use + is for bootstrapping the Keystone database. + """ + + def __init__(self, url, token, **kwargs): + """A plugin for static authentication with an existing token + + :param string url: Service endpoint + :param string token: Existing token + """ + super(TokenEndpoint, self).__init__() + self.endpoint = url + self.token = token + + def get_endpoint(self, session, **kwargs): + """Return the supplied endpoint""" + return self.endpoint + + def get_token(self, session): + """Return the supplied token""" + return self.token + + def get_auth_ref(self, session, **kwargs): + """Stub this method for compatibility""" + return None + + # Override this because it needs to be a class method... + @classmethod + def get_options(self): + options = super(TokenEndpoint, self).get_options() + + options.extend([ + # Maintain name 'url' for compatibility + cfg.StrOpt('url', + help='Specific service endpoint to use'), + cfg.StrOpt('token', + secret=True, + help='Authentication token to use'), + ]) + + return options diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 0542b473..ae38f160 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -29,6 +29,8 @@ from openstackclient.identity import client as identity_client LOG = logging.getLogger(__name__) +PLUGIN_MODULES = [] + class ClientCache(object): """Descriptor class for caching created client handles.""" @@ -53,20 +55,49 @@ class ClientManager(object): for o in auth.OPTIONS_LIST]: return self._auth_params[name[1:]] - def __init__(self, auth_options, api_version=None, verify=True): - + def __init__( + self, + auth_options, + api_version=None, + verify=True, + pw_func=None, + ): + """Set up a ClientManager + + :param auth_options: + Options collected from the command-line, environment, or wherever + :param api_version: + Dict of API versions: key is API name, value is the version + :param verify: + TLS certificate verification; may be a boolean to enable or disable + server certificate verification, or a filename of a CA certificate + bundle to be used in verification (implies True) + :param pw_func: + Callback function for asking the user for a password. The function + takes an optional string for the prompt ('Password: ' on None) and + returns a string containig the password + """ + + # If no plugin is named by the user, select one based on + # the supplied options if not auth_options.os_auth_plugin: - auth._guess_authentication_method(auth_options) - + auth_options.os_auth_plugin = auth.select_auth_plugin(auth_options) self._auth_plugin = auth_options.os_auth_plugin + + # Horrible hack alert...must handle prompt for null password if + # password auth is requested. + if (self._auth_plugin.endswith('password') and + not auth_options.os_password): + auth_options.os_password = pw_func() + self._url = auth_options.os_url self._auth_params = auth.build_auth_params(auth_options) self._region_name = auth_options.os_region_name self._api_version = api_version - self._service_catalog = None + self._auth_ref = None self.timing = auth_options.timing - # For compatability until all clients can be updated + # For compatibility until all clients can be updated if 'project_name' in self._auth_params: self._project_name = self._auth_params['project_name'] elif 'tenant_name' in self._auth_params: @@ -86,48 +117,49 @@ class ClientManager(object): root_logger = logging.getLogger('') LOG.setLevel(root_logger.getEffectiveLevel()) - self.session = None - if not self._url: - LOG.debug('Using auth plugin: %s' % self._auth_plugin) - auth_plugin = base.get_plugin_class(self._auth_plugin) - self.auth = auth_plugin.load_from_options(**self._auth_params) - # needed by SAML authentication - request_session = requests.session() - self.session = session.Session( - auth=self.auth, - session=request_session, - verify=verify, - ) - - self.auth_ref = None - if not self._auth_plugin.endswith("token") and not self._url: - LOG.debug("Populate other password flow attributes") - self.auth_ref = self.session.auth.get_auth_ref(self.session) - self._token = self.session.auth.get_token(self.session) - self._service_catalog = self.auth_ref.service_catalog - else: - self._token = self._auth_params.get('token') + LOG.debug('Using auth plugin: %s' % self._auth_plugin) + auth_plugin = base.get_plugin_class(self._auth_plugin) + self.auth = auth_plugin.load_from_options(**self._auth_params) + # needed by SAML authentication + request_session = requests.session() + self.session = session.Session( + auth=self.auth, + session=request_session, + verify=verify, + ) return - def get_endpoint_for_service_type(self, service_type): + @property + def auth_ref(self): + """Dereference will trigger an auth if it hasn't already""" + if not self._auth_ref: + LOG.debug("Get auth_ref") + self._auth_ref = self.auth.get_auth_ref(self.session) + return self._auth_ref + + def get_endpoint_for_service_type(self, service_type, region_name=None): """Return the endpoint URL for the service type.""" # See if we are using password flow auth, i.e. we have a # service catalog to select endpoints from - if self._service_catalog: - endpoint = self._service_catalog.url_for( - service_type=service_type) + if self.auth_ref: + endpoint = self.auth_ref.service_catalog.url_for( + service_type=service_type, + region_name=region_name, + ) else: - # Hope we were given the correct URL. - endpoint = self._auth_url or self._url + # Get the passed endpoint directly from the auth plugin + endpoint = self.auth.get_endpoint(self.session) return endpoint -def get_extension_modules(group): - """Add extension clients""" +# Plugin Support + +def get_plugin_modules(group): + """Find plugin entry points""" mod_list = [] for ep in pkg_resources.iter_entry_points(group): - LOG.debug('found extension %r', ep.name) + LOG.debug('Found plugin %r', ep.name) __import__(ep.module_name) module = sys.modules[ep.module_name] @@ -136,6 +168,7 @@ def get_extension_modules(group): if init_func: init_func('x') + # Add the plugin to the ClientManager setattr( ClientManager, module.API_NAME, @@ -144,3 +177,22 @@ def get_extension_modules(group): ), ) return mod_list + + +def build_plugin_option_parser(parser): + """Add plugin options to the parser""" + + # Loop through extensions to get parser additions + for mod in PLUGIN_MODULES: + parser = mod.build_option_parser(parser) + return parser + + +# Get list of base plugin modules +PLUGIN_MODULES = get_plugin_modules( + 'openstack.cli.base', +) +# Append list of external plugin modules +PLUGIN_MODULES.extend(get_plugin_modules( + 'openstack.cli.extension', +)) diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py index 9901ea20..2d9575d9 100644 --- a/openstackclient/common/commandmanager.py +++ b/openstackclient/common/commandmanager.py @@ -16,6 +16,7 @@ """Modify cliff.CommandManager""" import logging +import pkg_resources import cliff.commandmanager @@ -46,3 +47,17 @@ class CommandManager(cliff.commandmanager.CommandManager): def get_command_groups(self): """Returns a list of the loaded command groups""" return self.group_list + + def get_command_names(self, group=None): + """Returns a list of commands loaded for the specified group""" + group_list = [] + if group is not None: + for ep in pkg_resources.iter_entry_points(group): + cmd_name = ( + ep.name.replace('_', ' ') + if self.convert_underscores + else ep.name + ) + group_list.append(cmd_name) + return group_list + return self.commands.keys() diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 7f9c52db..356cdca3 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -19,9 +19,25 @@ import logging import six import sys +from cliff import lister from cliff import show +class ListCommand(lister.Lister): + """List recognized commands by group""" + + auth_required = False + log = logging.getLogger(__name__ + '.ListCommand') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + cm = self.app.command_manager + groups = cm.get_command_groups() + + columns = ('Command Group', 'Commands') + return (columns, ((c, cm.get_command_names(group=c)) for c in groups)) + + class ListModule(show.ShowOne): """List module versions""" diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index dc50507e..c87bbee7 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -43,34 +43,14 @@ def make_client(instance): http_log_debug = utils.get_effective_log_level() <= logging.DEBUG extensions = [extension.Extension('list_extensions', list_extensions)] + client = compute_client( - username=instance._username, - api_key=instance._password, - project_id=instance._project_name, - auth_url=instance._auth_url, - cacert=instance._cacert, - insecure=instance._insecure, - region_name=instance._region_name, - # FIXME(dhellmann): get endpoint_type from option? - endpoint_type='publicURL', + session=instance.session, extensions=extensions, - service_type=API_NAME, - # FIXME(dhellmann): what is service_name? - service_name='', http_log_debug=http_log_debug, timings=instance.timing, ) - # Populate the Nova client to skip another auth query to Identity - if instance._url: - # token flow - client.client.management_url = instance._url - else: - # password flow - client.client.management_url = instance.get_endpoint_for_service_type( - API_NAME) - client.client.service_catalog = instance._service_catalog - client.client.auth_token = instance._token return client diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index bc10a6d2..8050d120 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -44,27 +44,11 @@ def make_client(instance): API_VERSIONS) LOG.debug('Instantiating identity client: %s', identity_client) - # TODO(dtroyer): Something doesn't like the session.auth when using - # token auth, chase that down. - if instance._url: - LOG.debug('Using service token auth') - client = identity_client( - endpoint=instance._url, - token=instance._auth_params['token'], - cacert=instance._cacert, - insecure=instance._insecure - ) - else: - LOG.debug('Using auth plugin: %s' % instance._auth_plugin) - client = identity_client( - session=instance.session, - cacert=instance._cacert, - ) + LOG.debug('Using auth plugin: %s' % instance._auth_plugin) + client = identity_client( + session=instance.session, + ) - # TODO(dtroyer): the identity v2 role commands use this yet, fix that - # so we can remove it - if not instance._url: - instance.auth_ref = instance.auth.get_auth_ref(instance.session) return client diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 458dce7c..e8848dde 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -141,9 +141,10 @@ class ShowService(show.ShowOne): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + auth_ref = self.app.client_manager.auth_ref if parsed_args.catalog: - endpoints = identity_client.service_catalog.get_endpoints( + endpoints = auth_ref.service_catalog.get_endpoints( service_type=parsed_args.service) for (service, service_endpoints) in six.iteritems(endpoints): if service_endpoints: diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index f1e17b85..4d688954 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -73,6 +73,7 @@ class CreateCredential(show.ShowOne): blob=parsed_args.data, project=project) + credential._info.pop('links') return zip(*sorted(six.iteritems(credential._info))) @@ -193,4 +194,5 @@ class ShowCredential(show.ShowOne): credential = utils.find_resource(identity_client.credentials, parsed_args.credential) + credential._info.pop('links') return zip(*sorted(six.iteritems(credential._info))) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 49397afc..d14da486 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -66,6 +66,7 @@ class CreateDomain(show.ShowOne): enabled=parsed_args.enabled, ) + domain._info.pop('links') return zip(*sorted(six.iteritems(domain._info))) @@ -187,4 +188,5 @@ class ShowDomain(show.ShowOne): domain = utils.find_resource(identity_client.domains, parsed_args.domain) + domain._info.pop('links') return zip(*sorted(six.iteritems(domain._info))) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 39798b2d..0c077c5a 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -81,6 +81,7 @@ class CreateEndpoint(show.ShowOne): ) info = {} + endpoint._info.pop('links') info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type @@ -258,6 +259,7 @@ class ShowEndpoint(show.ShowOne): service = common.find_service(identity_client, endpoint.service_id) info = {} + endpoint._info.pop('links') info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index bdb4e029..d2ffca27 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -138,9 +138,8 @@ class CreateGroup(show.ShowOne): domain=domain, description=parsed_args.description) - info = {} - info.update(group._info) - return zip(*sorted(six.iteritems(info))) + group._info.pop('links') + return zip(*sorted(six.iteritems(group._info))) class DeleteGroup(command.Command): @@ -340,4 +339,5 @@ class ShowGroup(show.ShowOne): group = utils.find_resource(identity_client.groups, parsed_args.group) + group._info.pop('links') return zip(*sorted(six.iteritems(group._info))) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 87f3cbe9..802880bf 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -55,6 +55,7 @@ class CreatePolicy(show.ShowOne): blob=blob, type=parsed_args.type ) + policy._info.pop('links') return zip(*sorted(six.iteritems(policy._info))) @@ -173,4 +174,5 @@ class ShowPolicy(show.ShowOne): policy = utils.find_resource(identity_client.policies, parsed_args.policy) + policy._info.pop('links') return zip(*sorted(six.iteritems(policy._info))) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 7b3c281c..1cdeb150 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -94,9 +94,8 @@ class CreateProject(show.ShowOne): **kwargs ) - info = {} - info.update(project._info) - return zip(*sorted(six.iteritems(info))) + project._info.pop('links') + return zip(*sorted(six.iteritems(project._info))) class DeleteProject(command.Command): @@ -229,7 +228,7 @@ class SetProject(command.Command): parsed_args.project, ) - kwargs = project._info + kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.domain: @@ -243,13 +242,6 @@ class SetProject(command.Command): kwargs['enabled'] = False if parsed_args.property: kwargs.update(parsed_args.property) - if 'id' in kwargs: - del kwargs['id'] - if 'domain_id' in kwargs: - # Hack around broken Identity API arg names - kwargs.update( - {'domain': kwargs.pop('domain_id')} - ) identity_client.projects.update(project.id, **kwargs) return @@ -287,4 +279,5 @@ class ShowProject(show.ShowOne): project = utils.find_resource(identity_client.projects, parsed_args.project) + project._info.pop('links') return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index d8de7c22..a3c24b7a 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -157,6 +157,7 @@ class CreateRole(show.ShowOne): role = identity_client.roles.create(name=parsed_args.name) + role._info.pop('links') return zip(*sorted(six.iteritems(role._info))) @@ -472,4 +473,5 @@ class ShowRole(show.ShowOne): parsed_args.role, ) + role._info.pop('links') return zip(*sorted(six.iteritems(role._info))) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 88301edc..4f622269 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -70,6 +70,7 @@ class CreateService(show.ShowOne): enabled=enabled, ) + service._info.pop('links') return zip(*sorted(six.iteritems(service._info))) @@ -161,7 +162,7 @@ class SetService(command.Command): service = common.find_service(identity_client, parsed_args.service) - kwargs = service._info + kwargs = {} if parsed_args.type: kwargs['type'] = parsed_args.type if parsed_args.name: @@ -170,8 +171,6 @@ class SetService(command.Command): kwargs['enabled'] = True if parsed_args.disable: kwargs['enabled'] = False - if 'id' in kwargs: - del kwargs['id'] identity_client.services.update( service.id, @@ -200,4 +199,5 @@ class ShowService(show.ShowOne): service = common.find_service(identity_client, parsed_args.service) + service._info.pop('links') return zip(*sorted(six.iteritems(service._info))) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index aca5c669..5b09b69f 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -159,9 +159,7 @@ class IssueToken(show.ShowOne): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - session = self.app.client_manager.identity.session - - token = session.auth.auth_ref.service_catalog.get_token() + token = self.app.client_manager.auth_ref.service_catalog.get_token() if 'tenant_id' in token: token['project_id'] = token.pop('tenant_id') return zip(*sorted(six.iteritems(token))) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 10921219..9a3e00b8 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -116,9 +116,8 @@ class CreateUser(show.ShowOne): enabled=enabled ) - info = {} - info.update(user._info) - return zip(*sorted(six.iteritems(info))) + user._info.pop('links') + return zip(*sorted(six.iteritems(user._info))) class DeleteUser(command.Command): @@ -382,4 +381,5 @@ class ShowUser(show.ShowOne): user = utils.find_resource(identity_client.users, parsed_args.user) + user._info.pop('links') return zip(*sorted(six.iteritems(user._info))) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index a23d349e..c55ff853 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -40,12 +40,14 @@ def make_client(instance): API_VERSIONS) LOG.debug('Instantiating image client: %s', image_client) - if not instance._url: - instance._url = instance.get_endpoint_for_service_type(API_NAME) + endpoint = instance.get_endpoint_for_service_type( + API_NAME, + region_name=instance._region_name, + ) return image_client( - instance._url, - token=instance._token, + endpoint, + token=instance.auth.get_token(instance.session), cacert=instance._cacert, insecure=instance._insecure, ) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index d3102da1..bb3e1b23 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -34,16 +34,19 @@ def make_client(instance): API_VERSIONS) LOG.debug('Instantiating network client: %s', network_client) - if not instance._url: - instance._url = instance.get_endpoint_for_service_type("network") + endpoint = instance.get_endpoint_for_service_type( + API_NAME, + region_name=instance._region_name, + ) + return network_client( username=instance._username, tenant_name=instance._project_name, password=instance._password, region_name=instance._region_name, auth_url=instance._auth_url, - endpoint_url=instance._url, - token=instance._token, + endpoint_url=endpoint, + token=instance.auth.get_token(instance.session), insecure=instance._insecure, ca_cert=instance._cacert, ) diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 1ac905c3..beb7c04f 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -33,10 +33,10 @@ API_VERSIONS = { def make_client(instance): """Returns an object-store API client.""" - if instance._url: - endpoint = instance._url - else: - endpoint = instance.get_endpoint_for_service_type("object-store") + endpoint = instance.get_endpoint_for_service_type( + 'object-store', + region_name=instance._region_name, + ) client = object_store_v1.APIv1( session=instance.session, diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 626e3f7d..e671ecc3 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -36,6 +36,30 @@ from openstackclient.common import utils DEFAULT_DOMAIN = 'default' +def prompt_for_password(prompt=None): + """Prompt user for a password + + Propmpt for a password if stdin is a tty. + """ + + if not prompt: + prompt = 'Password: ' + pw = None + # If stdin is a tty, try prompting for the password + if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): + # Check for Ctl-D + try: + pw = getpass.getpass(prompt) + except EOFError: + pass + # No password because we did't have a tty or nothing was entered + if not pw: + raise exc.CommandError( + "No password entered, or found via --os-password or OS_PASSWORD", + ) + return pw + + class OpenStackShell(app.App): CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' @@ -68,18 +92,7 @@ class OpenStackShell(app.App): # Assume TLS host certificate verification is enabled self.verify = True - # Get list of base modules - self.ext_modules = clientmanager.get_extension_modules( - 'openstack.cli.base', - ) - # Append list of extension modules - self.ext_modules.extend(clientmanager.get_extension_modules( - 'openstack.cli.extension', - )) - - # Loop through extensions to get parser additions - for mod in self.ext_modules: - self.parser = mod.build_option_parser(self.parser) + self.client_manager = None # NOTE(dtroyer): This hack changes the help action that Cliff # automatically adds to the parser so we can defer @@ -170,6 +183,7 @@ class OpenStackShell(app.App): parser = super(OpenStackShell, self).build_option_parser( description, version) + # service token auth argument parser.add_argument( '--os-url', @@ -214,109 +228,7 @@ class OpenStackShell(app.App): help="Print API call timing info", ) - return parser - - def authenticate_user(self): - """Verify the required authentication credentials are present""" - - self.log.debug("validating authentication options") - - # Assuming all auth plugins will be named in the same fashion, - # ie vXpluginName - if (not self.options.os_url and - self.options.os_auth_plugin.startswith('v') and - self.options.os_auth_plugin[1] != - self.options.os_identity_api_version[0]): - raise exc.CommandError( - "Auth plugin %s not compatible" - " with requested API version" % self.options.os_auth_plugin - ) - # TODO(mhu) All these checks should be exposed at the plugin level - # or just dropped altogether, as the client instantiation will fail - # anyway - if self.options.os_url and not self.options.os_token: - # service token needed - raise exc.CommandError( - "You must provide a service token via" - " either --os-token or env[OS_TOKEN]") - - if (self.options.os_auth_plugin.endswith('token') and - (self.options.os_token or self.options.os_auth_url)): - # Token flow auth takes priority - if not self.options.os_token: - raise exc.CommandError( - "You must provide a token via" - " either --os-token or env[OS_TOKEN]") - - if not self.options.os_auth_url: - raise exc.CommandError( - "You must provide a service URL via" - " either --os-auth-url or env[OS_AUTH_URL]") - - if (not self.options.os_url and - not self.options.os_auth_plugin.endswith('token')): - # Validate password flow auth - if not self.options.os_username: - raise exc.CommandError( - "You must provide a username via" - " either --os-username or env[OS_USERNAME]") - - if not self.options.os_password: - # No password, if we've got a tty, try prompting for it - if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): - # Check for Ctl-D - try: - self.options.os_password = getpass.getpass() - except EOFError: - pass - # No password because we did't have a tty or the - # user Ctl-D when prompted? - if not self.options.os_password: - raise exc.CommandError( - "You must provide a password via" - " either --os-password, or env[OS_PASSWORD], " - " or prompted response") - - if not ((self.options.os_project_id - or self.options.os_project_name) or - (self.options.os_domain_id - or self.options.os_domain_name) or - self.options.os_trust_id): - if self.options.os_auth_plugin.endswith('password'): - raise exc.CommandError( - "You must provide authentication scope as a project " - "or a domain via --os-project-id " - "or env[OS_PROJECT_ID], " - "--os-project-name or env[OS_PROJECT_NAME], " - "--os-domain-id or env[OS_DOMAIN_ID], or" - "--os-domain-name or env[OS_DOMAIN_NAME], or " - "--os-trust-id or env[OS_TRUST_ID].") - - if not self.options.os_auth_url: - raise exc.CommandError( - "You must provide an auth url via" - " either --os-auth-url or via env[OS_AUTH_URL]") - - if (self.options.os_trust_id and - self.options.os_identity_api_version != '3'): - raise exc.CommandError( - "Trusts can only be used with Identity API v3") - - if (self.options.os_trust_id and - ((self.options.os_project_id - or self.options.os_project_name) or - (self.options.os_domain_id - or self.options.os_domain_name))): - raise exc.CommandError( - "Authentication cannot be scoped to multiple targets. " - "Pick one of project, domain or trust.") - - self.client_manager = clientmanager.ClientManager( - auth_options=self.options, - verify=self.verify, - api_version=self.api_version, - ) - return + return clientmanager.build_plugin_option_parser(parser) def initialize_app(self, argv): """Global app init bits: @@ -332,7 +244,7 @@ class OpenStackShell(app.App): self.default_domain = self.options.os_default_domain # Loop through extensions to get API versions - for mod in self.ext_modules: + for mod in clientmanager.PLUGIN_MODULES: version_opt = getattr(self.options, mod.API_VERSION_OPTION, None) if version_opt: api = mod.API_NAME @@ -374,19 +286,23 @@ class OpenStackShell(app.App): else: self.verify = not self.options.insecure + self.client_manager = clientmanager.ClientManager( + auth_options=self.options, + verify=self.verify, + api_version=self.api_version, + pw_func=prompt_for_password, + ) + def prepare_to_run_command(self, cmd): """Set up auth and API versions""" self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__) - if not cmd.auth_required: - return - if cmd.best_effort: + if cmd.auth_required and cmd.best_effort: try: - self.authenticate_user() + # Trigger the Identity client to initialize + self.client_manager.auth_ref except Exception: pass - else: - self.authenticate_user() return def clean_up(self, cmd, result, err): @@ -418,12 +334,6 @@ class OpenStackShell(app.App): targs = tparser.parse_args(['-f', format]) tcmd.run(targs) - def interact(self): - # NOTE(dtroyer): Maintain the old behaviour for interactive use as - # this path does not call prepare_to_run_command() - self.authenticate_user() - super(OpenStackShell, self).interact() - def main(argv=sys.argv[1:]): return OpenStackShell().run(argv) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 18461fb7..a7b13c6c 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -16,8 +16,8 @@ import mock from requests_mock.contrib import fixture from keystoneclient.auth.identity import v2 as auth_v2 -from keystoneclient.openstack.common import jsonutils from keystoneclient import service_catalog +from oslo.serialization import jsonutils from openstackclient.api import auth from openstackclient.common import clientmanager @@ -76,6 +76,30 @@ class TestClientManager(utils.TestCase): url=fakes.AUTH_URL, verb='GET') + def test_client_manager_token_endpoint(self): + + client_manager = clientmanager.ClientManager( + auth_options=FakeOptions(os_token=fakes.AUTH_TOKEN, + os_url=fakes.AUTH_URL, + os_auth_plugin='token_endpoint'), + api_version=API_VERSION, + verify=True + ) + self.assertEqual( + fakes.AUTH_URL, + client_manager._url, + ) + self.assertEqual( + fakes.AUTH_TOKEN, + client_manager.auth.get_token(None), + ) + self.assertIsInstance( + client_manager.auth, + auth.TokenEndpoint, + ) + self.assertFalse(client_manager._insecure) + self.assertTrue(client_manager._verify) + def test_client_manager_token(self): client_manager = clientmanager.ClientManager( @@ -87,10 +111,6 @@ class TestClientManager(utils.TestCase): ) self.assertEqual( - fakes.AUTH_TOKEN, - client_manager._token, - ) - self.assertEqual( fakes.AUTH_URL, client_manager._auth_url, ) @@ -136,12 +156,8 @@ class TestClientManager(utils.TestCase): client_manager.auth_ref, ) self.assertEqual( - fakes.AUTH_TOKEN, - client_manager._token, - ) - self.assertEqual( dir(SERVICE_CATALOG), - dir(client_manager._service_catalog), + dir(client_manager.auth_ref.service_catalog), ) def stub_auth(self, json=None, url=None, verb=None, **kwargs): @@ -176,8 +192,7 @@ class TestClientManager(utils.TestCase): self.assertTrue(client_manager._verify) self.assertEqual('cafile', client_manager._cacert) - def _client_manager_guess_auth_plugin(self, auth_params, - api_version, auth_plugin): + def _select_auth_plugin(self, auth_params, api_version, auth_plugin): auth_params['os_auth_plugin'] = auth_plugin auth_params['os_identity_api_version'] = api_version client_manager = clientmanager.ClientManager( @@ -190,25 +205,25 @@ class TestClientManager(utils.TestCase): client_manager._auth_plugin, ) - def test_client_manager_guess_auth_plugin(self): + def test_client_manager_select_auth_plugin(self): # test token auth params = dict(os_token=fakes.AUTH_TOKEN, os_auth_url=fakes.AUTH_URL) - self._client_manager_guess_auth_plugin(params, '2.0', 'v2token') - self._client_manager_guess_auth_plugin(params, '3', 'v3token') - self._client_manager_guess_auth_plugin(params, 'XXX', 'token') - # test service auth + self._select_auth_plugin(params, '2.0', 'v2token') + self._select_auth_plugin(params, '3', 'v3token') + self._select_auth_plugin(params, 'XXX', 'token') + # test token/endpoint auth params = dict(os_token=fakes.AUTH_TOKEN, os_url='test') - self._client_manager_guess_auth_plugin(params, 'XXX', '') + self._select_auth_plugin(params, 'XXX', 'token_endpoint') # test password auth params = dict(os_auth_url=fakes.AUTH_URL, os_username=fakes.USERNAME, os_password=fakes.PASSWORD) - self._client_manager_guess_auth_plugin(params, '2.0', 'v2password') - self._client_manager_guess_auth_plugin(params, '3', 'v3password') - self._client_manager_guess_auth_plugin(params, 'XXX', 'password') + self._select_auth_plugin(params, '2.0', 'v2password') + self._select_auth_plugin(params, '3', 'v3password') + self._select_auth_plugin(params, 'XXX', 'password') - def test_client_manager_guess_auth_plugin_failure(self): + def test_client_manager_select_auth_plugin_failure(self): self.assertRaises(exc.CommandError, clientmanager.ClientManager, auth_options=FakeOptions(os_auth_plugin=''), diff --git a/openstackclient/tests/common/test_commandmanager.py b/openstackclient/tests/common/test_commandmanager.py index ca9ee9a7..e7803a48 100644 --- a/openstackclient/tests/common/test_commandmanager.py +++ b/openstackclient/tests/common/test_commandmanager.py @@ -86,3 +86,20 @@ class TestCommandManager(utils.TestCase): gl = mgr.get_command_groups() self.assertEqual(['test', 'greek'], gl) + + def test_get_command_names(self): + mock_cmd_one = mock.Mock() + mock_cmd_one.name = 'one' + mock_cmd_two = mock.Mock() + mock_cmd_two.name = 'cmd two' + mock_pkg_resources = mock.Mock( + return_value=[mock_cmd_one, mock_cmd_two], + ) + with mock.patch( + 'pkg_resources.iter_entry_points', + mock_pkg_resources, + ) as iter_entry_points: + mgr = commandmanager.CommandManager('test') + assert iter_entry_points.called_once_with('test') + cmds = mgr.get_command_names('test') + self.assertEqual(['one', 'cmd two'], cmds) diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py index ce1592e4..6918c1b4 100644 --- a/openstackclient/tests/common/test_module.py +++ b/openstackclient/tests/common/test_module.py @@ -42,6 +42,38 @@ MODULES = { } +class TestCommandList(utils.TestCommand): + + def setUp(self): + super(TestCommandList, self).setUp() + + self.app.command_manager = mock.Mock() + self.app.command_manager.get_command_groups.return_value = ['test'] + self.app.command_manager.get_command_names.return_value = [ + 'one', + 'cmd two', + ] + + # Get the command object to test + self.cmd = osc_module.ListCommand(self.app, None) + + def test_command_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + collist = ('Command Group', 'Commands') + self.assertEqual(collist, columns) + datalist = (( + 'test', + ['one', 'cmd two'], + ), ) + self.assertEqual(datalist, tuple(data)) + + @mock.patch.dict( 'openstackclient.common.module.sys.modules', values=MODULES, diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 1ca1c55d..5844d160 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -18,6 +18,7 @@ import mock from openstackclient.tests import fakes from openstackclient.tests import utils +base_url = 'http://identity:5000/v3/' domain_id = 'd1' domain_name = 'oftheking' @@ -28,6 +29,7 @@ DOMAIN = { 'name': domain_name, 'description': domain_description, 'enabled': True, + 'links': base_url + 'domains/' + domain_id, } group_id = 'gr-010' @@ -36,6 +38,7 @@ group_name = 'spencer davis' GROUP = { 'id': group_id, 'name': group_name, + 'links': base_url + 'groups/' + group_id, } mapping_id = 'test_mapping' @@ -107,6 +110,7 @@ PROJECT = { 'description': project_description, 'enabled': True, 'domain_id': domain_id, + 'links': base_url + 'projects/' + project_id, } PROJECT_2 = { @@ -115,6 +119,7 @@ PROJECT_2 = { 'description': project_description + 'plus four more', 'enabled': True, 'domain_id': domain_id, + 'links': base_url + 'projects/' + project_id, } role_id = 'r1' @@ -123,6 +128,7 @@ role_name = 'roller' ROLE = { 'id': role_id, 'name': role_name, + 'links': base_url + 'roles/' + role_id, } service_id = 's-123' @@ -134,6 +140,7 @@ SERVICE = { 'name': service_name, 'type': service_type, 'enabled': True, + 'links': base_url + 'services/' + service_id, } endpoint_id = 'e-123' @@ -148,6 +155,7 @@ ENDPOINT = { 'interface': endpoint_interface, 'service_id': service_id, 'enabled': True, + 'links': base_url + 'endpoints/' + endpoint_id, } user_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' @@ -162,15 +170,16 @@ USER = { 'email': user_email, 'enabled': True, 'domain_id': domain_id, + 'links': base_url + 'users/' + user_id, } token_expires = '2014-01-01T00:00:00Z' token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt' -TOKEN_WITH_TENANT_ID = { +TOKEN_WITH_PROJECT_ID = { 'expires': token_expires, 'id': token_id, - 'tenant_id': project_id, + 'project_id': project_id, 'user_id': user_id, } diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 2e7bc54b..1060a277 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -533,9 +533,6 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, - 'domain': identity_fakes.domain_id, - 'enabled': True, 'name': 'qwerty', } # ProjectManager.update(project, name=, domain=, description=, @@ -564,9 +561,6 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { 'description': 'new desc', - 'domain': identity_fakes.domain_id, - 'enabled': True, - 'name': identity_fakes.project_name, } self.projects_mock.update.assert_called_with( identity_fakes.project_id, @@ -590,10 +584,7 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, - 'domain': identity_fakes.domain_id, 'enabled': True, - 'name': identity_fakes.project_name, } self.projects_mock.update.assert_called_with( identity_fakes.project_id, @@ -617,10 +608,7 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, - 'domain': identity_fakes.domain_id, 'enabled': False, - 'name': identity_fakes.project_name, } self.projects_mock.update.assert_called_with( identity_fakes.project_id, @@ -644,10 +632,6 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, - 'domain': identity_fakes.domain_id, - 'enabled': True, - 'name': identity_fakes.project_name, 'fee': 'fi', 'fo': 'fum', } diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 6733f7fa..57db77b1 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -267,9 +267,7 @@ class TestServiceSet(TestService): # Set expected values kwargs = { - 'name': identity_fakes.service_name, 'type': identity_fakes.service_type, - 'enabled': True, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( @@ -297,8 +295,6 @@ class TestServiceSet(TestService): # Set expected values kwargs = { 'name': identity_fakes.service_name, - 'type': identity_fakes.service_type, - 'enabled': True, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( @@ -325,8 +321,6 @@ class TestServiceSet(TestService): # Set expected values kwargs = { - 'name': identity_fakes.service_name, - 'type': identity_fakes.service_type, 'enabled': True, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) @@ -354,8 +348,6 @@ class TestServiceSet(TestService): # Set expected values kwargs = { - 'name': identity_fakes.service_name, - 'type': identity_fakes.service_type, 'enabled': False, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index dbe85555..f43b6f5f 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -13,6 +13,8 @@ # under the License. # +import mock + from openstackclient.identity.v3 import token from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -23,9 +25,9 @@ class TestToken(identity_fakes.TestIdentityv3): super(TestToken, self).setUp() # Get a shortcut to the Service Catalog Mock - session = self.app.client_manager.identity.session - self.sc_mock = session.auth.auth_ref.service_catalog - self.sc_mock.reset_mock() + self.sc_mock = mock.Mock() + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.service_catalog = self.sc_mock class TestTokenIssue(TestToken): @@ -40,7 +42,7 @@ class TestTokenIssue(TestToken): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.sc_mock.get_token.return_value = \ - identity_fakes.TOKEN_WITH_TENANT_ID + identity_fakes.TOKEN_WITH_PROJECT_ID # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index f71fbe8b..f4e2decb 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -49,29 +49,13 @@ def make_client(instance): http_log_debug = utils.get_effective_log_level() <= logging.DEBUG extensions = [extension.Extension('list_extensions', list_extensions)] + client = volume_client( - username=instance._username, - api_key=instance._password, - project_id=instance._project_name, - auth_url=instance._auth_url, - cacert=instance._cacert, - insecure=instance._insecure, - region_name=instance._region_name, + session=instance.session, extensions=extensions, http_log_debug=http_log_debug, ) - # Populate the Cinder client to skip another auth query to Identity - if instance._url: - # token flow - client.client.management_url = instance._url - else: - # password flow - client.client.management_url = instance.get_endpoint_for_service_type( - API_NAME) - client.client.service_catalog = instance._service_catalog - client.client.auth_token = instance._token - return client |
