summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/auth.py69
-rw-r--r--openstackclient/common/clientmanager.py124
-rw-r--r--openstackclient/common/commandmanager.py15
-rw-r--r--openstackclient/common/module.py16
-rw-r--r--openstackclient/compute/client.py24
-rw-r--r--openstackclient/identity/client.py24
-rw-r--r--openstackclient/identity/v2_0/service.py3
-rw-r--r--openstackclient/identity/v3/credential.py2
-rw-r--r--openstackclient/identity/v3/domain.py2
-rw-r--r--openstackclient/identity/v3/endpoint.py2
-rw-r--r--openstackclient/identity/v3/group.py6
-rw-r--r--openstackclient/identity/v3/policy.py2
-rw-r--r--openstackclient/identity/v3/project.py15
-rw-r--r--openstackclient/identity/v3/role.py2
-rw-r--r--openstackclient/identity/v3/service.py6
-rw-r--r--openstackclient/identity/v3/token.py4
-rw-r--r--openstackclient/identity/v3/user.py6
-rw-r--r--openstackclient/image/client.py10
-rw-r--r--openstackclient/network/client.py11
-rw-r--r--openstackclient/object/client.py8
-rw-r--r--openstackclient/shell.py166
-rw-r--r--openstackclient/tests/common/test_clientmanager.py59
-rw-r--r--openstackclient/tests/common/test_commandmanager.py17
-rw-r--r--openstackclient/tests/common/test_module.py32
-rw-r--r--openstackclient/tests/identity/v3/fakes.py13
-rw-r--r--openstackclient/tests/identity/v3/test_project.py16
-rw-r--r--openstackclient/tests/identity/v3/test_service.py8
-rw-r--r--openstackclient/tests/identity/v3/test_token.py10
-rw-r--r--openstackclient/volume/client.py20
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