diff --git a/novaclient/client.py b/novaclient/client.py index 0b9aeeecc..ecb755065 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -64,8 +64,9 @@ class HTTPClient(object): os_cache=False, no_cache=True, http_log_debug=False, auth_system='keystone', auth_plugin=None, auth_token=None, - cacert=None, tenant_id=None): + cacert=None, tenant_id=None, user_id=None): self.user = user + self.user_id = user_id self.password = password self.projectid = projectid self.tenant_id = tenant_id @@ -456,6 +457,10 @@ class HTTPClient(object): if self.auth_token: body = {"auth": { "token": {"id": self.auth_token}}} + elif self.user_id: + body = {"auth": { + "passwordCredentials": {"userId": self.user_id, + "password": self._get_password()}}} else: body = {"auth": { "passwordCredentials": {"username": self.user, diff --git a/novaclient/shell.py b/novaclient/shell.py index 919aa21ba..fcbf83e3d 100644 --- a/novaclient/shell.py +++ b/novaclient/shell.py @@ -285,6 +285,11 @@ class OpenStackComputeShell(object): parser.add_argument('--os_username', help=argparse.SUPPRESS) + parser.add_argument('--os-user-id', + metavar='', + default=utils.env('OS_USER_ID'), + help=_('Defaults to env[OS_USER_ID].')) + parser.add_argument('--os-password', metavar='', default=utils.env('OS_PASSWORD', 'NOVA_PASSWORD'), @@ -550,6 +555,7 @@ class OpenStackComputeShell(object): return 0 os_username = args.os_username + os_user_id = args.os_user_id os_password = None # Fetched and set later as needed os_tenant_name = args.os_tenant_name os_tenant_id = args.os_tenant_id @@ -606,9 +612,10 @@ class OpenStackComputeShell(object): auth_plugin.parse_opts(args) if not auth_plugin or not auth_plugin.opts: - if not os_username: + if not os_username and not os_user_id: raise exc.CommandError(_("You must provide a username " - "via either --os-username or env[OS_USERNAME]")) + "or user id via --os-username, --os-user-id, " + "env[OS_USERNAME] or env[OS_USER_ID]")) if not os_tenant_name and not os_tenant_id: raise exc.CommandError(_("You must provide a tenant name " @@ -639,8 +646,9 @@ class OpenStackComputeShell(object): raise exc.CommandError(_("You must provide an auth url " "via either --os-auth-url or env[OS_AUTH_URL]")) - self.cs = client.Client(options.os_compute_api_version, os_username, - os_password, os_tenant_name, tenant_id=os_tenant_id, + self.cs = client.Client(options.os_compute_api_version, + os_username, os_password, os_tenant_name, + tenant_id=os_tenant_id, user_id=os_user_id, auth_url=os_auth_url, insecure=insecure, region_name=os_region_name, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, diff --git a/novaclient/tests/test_shell.py b/novaclient/tests/test_shell.py index 7cbb89b9b..8994cb34b 100644 --- a/novaclient/tests/test_shell.py +++ b/novaclient/tests/test_shell.py @@ -32,7 +32,7 @@ FAKE_ENV = {'OS_USERNAME': 'username', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where'} -FAKE_ENV2 = {'OS_USERNAME': 'username', +FAKE_ENV2 = {'OS_USER_ID': 'user_id', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where'} @@ -133,25 +133,38 @@ class ShellTest(utils.TestCase): matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_no_username(self): - required = ('You must provide a username' - ' via either --os-username or env[OS_USERNAME]',) + required = ('You must provide a username or user id' + ' via --os-username, --os-user-id,' + ' env[OS_USERNAME] or env[OS_USER_ID]') self.make_env(exclude='OS_USERNAME') try: self.shell('list') except exceptions.CommandError as message: - self.assertEqual(required, message.args) + self.assertEqual(required, message.args[0]) + else: + self.fail('CommandError not raised') + + def test_no_user_id(self): + required = ('You must provide a username or user id' + ' via --os-username, --os-user-id,' + ' env[OS_USERNAME] or env[OS_USER_ID]') + self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2) + try: + self.shell('list') + except exceptions.CommandError as message: + self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_tenant_name(self): required = ('You must provide a tenant name or tenant id' ' via --os-tenant-name, --os-tenant-id,' - ' env[OS_TENANT_NAME] or env[OS_TENANT_ID]',) + ' env[OS_TENANT_NAME] or env[OS_TENANT_ID]') self.make_env(exclude='OS_TENANT_NAME') try: self.shell('list') except exceptions.CommandError as message: - self.assertEqual(required, message.args) + self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py index efeb5c993..70c90f8b0 100644 --- a/novaclient/v1_1/client.py +++ b/novaclient/v1_1/client.py @@ -73,12 +73,13 @@ class Client(object): bypass_url=None, os_cache=False, no_cache=True, http_log_debug=False, auth_system='keystone', auth_plugin=None, auth_token=None, - cacert=None, tenant_id=None): + cacert=None, tenant_id=None, user_id=None): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key self.projectid = project_id self.tenant_id = tenant_id + self.user_id = user_id self.flavors = flavors.FlavorManager(self) self.flavor_access = flavor_access.FlavorAccessManager(self) self.images = images.ImageManager(self) @@ -126,6 +127,7 @@ class Client(object): self.client = client.HTTPClient(username, password, + user_id=user_id, projectid=project_id, tenant_id=tenant_id, auth_url=auth_url, diff --git a/novaclient/v3/client.py b/novaclient/v3/client.py index f26fdcf99..74d2f9eb5 100644 --- a/novaclient/v3/client.py +++ b/novaclient/v3/client.py @@ -59,9 +59,10 @@ class Client(object): bypass_url=None, os_cache=False, no_cache=True, http_log_debug=False, auth_system='keystone', auth_plugin=None, auth_token=None, - cacert=None, tenant_id=None): + cacert=None, tenant_id=None, user_id=None): self.projectid = project_id self.tenant_id = tenant_id + self.user_id = user_id self.os_cache = os_cache or not no_cache #TODO(bnemec): Add back in v3 extensions self.agents = agents.AgentsManager(self) @@ -91,6 +92,7 @@ class Client(object): self.client = client.HTTPClient(username, password, + user_id=user_id, projectid=project_id, tenant_id=tenant_id, auth_url=auth_url,