Fix shell operation with --os-auth-token
Currently the --os-auth-token/env[OS_AUTH_TOKEN] case doesn't work, as we always expect a username/password to create the connection to keystone. This enables the client to be used with only a token and tenant (which is required for keystoneclient to retrieve the catalog) specified, e.g: heat --os-auth-url http://127.0.0.1:35357/v2.0/ \ --os-auth-token <a keystone token> \ --os-tenant-id <tenant ID> stack-list Change-Id: I478ce178f44e42c68153f1b347c6144f0a133f5b Partial-Bug: #1252248
This commit is contained in:
@@ -219,15 +219,22 @@ class HeatShell(object):
|
||||
:param tenant_id: unique identifier of tenant
|
||||
:param tenant_name: name of tenant
|
||||
:param auth_url: endpoint to authenticate against
|
||||
:param token: token to use instead of username/password
|
||||
"""
|
||||
kc_args = {'auth_url': kwargs.get('auth_url'),
|
||||
'insecure': kwargs.get('insecure'),
|
||||
'username': kwargs.get('username'),
|
||||
'password': kwargs.get('password')}
|
||||
'insecure': kwargs.get('insecure')}
|
||||
|
||||
if kwargs.get('tenant_id'):
|
||||
kc_args['tenant_id'] = kwargs.get('tenant_id')
|
||||
else:
|
||||
kc_args['tenant_name'] = kwargs.get('tenant_name')
|
||||
|
||||
if kwargs.get('token'):
|
||||
kc_args['token'] = kwargs.get('token')
|
||||
else:
|
||||
kc_args['username'] = kwargs.get('username')
|
||||
kc_args['password'] = kwargs.get('password')
|
||||
|
||||
return ksclient.Client(**kc_args)
|
||||
|
||||
def _get_endpoint(self, client, **kwargs):
|
||||
@@ -280,18 +287,22 @@ class HeatShell(object):
|
||||
self.do_help(args)
|
||||
return 0
|
||||
|
||||
if not args.os_username:
|
||||
if not args.os_username and not args.os_auth_token:
|
||||
raise exc.CommandError("You must provide a username via"
|
||||
" either --os-username or env[OS_USERNAME]")
|
||||
" either --os-username or env[OS_USERNAME]"
|
||||
" or a token via --os-auth-token or"
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if not args.os_password:
|
||||
if not args.os_password and not args.os_auth_token:
|
||||
raise exc.CommandError("You must provide a password via"
|
||||
" either --os-password or env[OS_PASSWORD]")
|
||||
" either --os-password or env[OS_PASSWORD]"
|
||||
" or a token via --os-auth-token or"
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if not (args.os_tenant_id or args.os_tenant_name):
|
||||
raise exc.CommandError("You must provide a tenant_id via"
|
||||
" either --os-tenant-id or via "
|
||||
"env[OS_TENANT_ID]")
|
||||
" either --os-tenant-id or via"
|
||||
" env[OS_TENANT_ID]")
|
||||
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError("You must provide an auth url via"
|
||||
@@ -305,6 +316,7 @@ class HeatShell(object):
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'token': args.os_auth_token,
|
||||
'tenant_id': args.os_tenant_id,
|
||||
'tenant_name': args.os_tenant_name,
|
||||
'auth_url': args.os_auth_url,
|
||||
|
||||
@@ -18,12 +18,19 @@ from heatclient.v1 import client as v1client
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
|
||||
|
||||
def script_keystone_client():
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
password='password',
|
||||
tenant_name='tenant_name',
|
||||
username='username').AndReturn(FakeKeystone('abcd1234'))
|
||||
def script_keystone_client(token=None):
|
||||
if token:
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
tenant_id='tenant_id',
|
||||
token=token).AndReturn(FakeKeystone(token))
|
||||
else:
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
password='password',
|
||||
tenant_name='tenant_name',
|
||||
username='username').AndReturn(FakeKeystone(
|
||||
'abcd1234'))
|
||||
|
||||
|
||||
def script_heat_list():
|
||||
|
||||
@@ -114,6 +114,29 @@ class EnvVarTest(TestCase):
|
||||
self.shell_error('list', self.err)
|
||||
|
||||
|
||||
class EnvVarTestToken(TestCase):
|
||||
|
||||
scenarios = [
|
||||
('tenant_id', dict(
|
||||
remove='OS_TENANT_ID',
|
||||
err='You must provide a tenant_id')),
|
||||
('auth_url', dict(
|
||||
remove='OS_AUTH_URL',
|
||||
err='You must provide an auth url')),
|
||||
]
|
||||
|
||||
def test_missing_auth(self):
|
||||
|
||||
fake_env = {
|
||||
'OS_AUTH_TOKEN': 'atoken',
|
||||
'OS_TENANT_ID': 'tenant_id',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
fake_env[self.remove] = None
|
||||
self.set_fake_env(fake_env)
|
||||
self.shell_error('list', self.err)
|
||||
|
||||
|
||||
class ShellParamValidationTest(TestCase):
|
||||
|
||||
scenarios = [
|
||||
@@ -208,24 +231,16 @@ class ShellValidationTest(TestCase):
|
||||
'Need to specify exactly one of')
|
||||
|
||||
|
||||
class ShellTest(TestCase):
|
||||
class ShellBase(TestCase):
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
def setUp(self):
|
||||
super(ShellTest, self).setUp()
|
||||
super(ShellBase, self).setUp()
|
||||
self.m = mox.Mox()
|
||||
self.m.StubOutWithMock(ksclient, 'Client')
|
||||
self.m.StubOutWithMock(v1client.Client, 'json_request')
|
||||
self.m.StubOutWithMock(v1client.Client, 'raw_request')
|
||||
self.addCleanup(self.m.VerifyAll)
|
||||
self.addCleanup(self.m.UnsetStubs)
|
||||
fake_env = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.set_fake_env(fake_env)
|
||||
|
||||
# Some tests set exc.verbose = 1, so reset on cleanup
|
||||
def unset_exc_verbose():
|
||||
@@ -249,6 +264,12 @@ class ShellTest(TestCase):
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ShellTestCommon(ShellBase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTestCommon, self).setUp()
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
@@ -280,8 +301,28 @@ class ShellTest(TestCase):
|
||||
for r in required:
|
||||
self.assertRegexpMatches(help_text, r)
|
||||
|
||||
def test_list(self):
|
||||
|
||||
class ShellTestUserPass(ShellBase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTestUserPass, self).setUp()
|
||||
self._set_fake_env()
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
def _set_fake_env(self):
|
||||
fake_env = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.set_fake_env(fake_env)
|
||||
|
||||
def _script_keystone_client(self):
|
||||
fakes.script_keystone_client()
|
||||
|
||||
def test_list(self):
|
||||
self._script_keystone_client()
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
@@ -313,7 +354,7 @@ class ShellTest(TestCase):
|
||||
"title": "Not Found"
|
||||
}
|
||||
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
fakes.script_heat_error(json.dumps(resp_dict))
|
||||
|
||||
self.m.ReplayAll()
|
||||
@@ -336,7 +377,7 @@ class ShellTest(TestCase):
|
||||
"title": "Not Found"
|
||||
}
|
||||
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
fakes.script_heat_error(json.dumps(resp_dict))
|
||||
|
||||
self.m.ReplayAll()
|
||||
@@ -351,7 +392,7 @@ class ShellTest(TestCase):
|
||||
|
||||
def test_parsable_malformed_error(self):
|
||||
invalid_json = "ERROR: {Invalid JSON Error."
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
fakes.script_heat_error(invalid_json)
|
||||
self.m.ReplayAll()
|
||||
|
||||
@@ -371,7 +412,7 @@ class ShellTest(TestCase):
|
||||
"title": "Not Found"
|
||||
}
|
||||
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
fakes.script_heat_error(json.dumps(missing_message))
|
||||
self.m.ReplayAll()
|
||||
|
||||
@@ -392,7 +433,7 @@ class ShellTest(TestCase):
|
||||
"title": "Not Found"
|
||||
}
|
||||
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
fakes.script_heat_error(json.dumps(resp_dict))
|
||||
self.m.ReplayAll()
|
||||
|
||||
@@ -405,7 +446,7 @@ class ShellTest(TestCase):
|
||||
"ERROR: The Stack (bad) could not be found.\n")
|
||||
|
||||
def test_describe(self):
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
resp_dict = {"stack": {
|
||||
"id": "1",
|
||||
"stack_name": "teststack",
|
||||
@@ -437,7 +478,7 @@ class ShellTest(TestCase):
|
||||
self.assertRegexpMatches(list_text, r)
|
||||
|
||||
def test_template_show_cfn(self):
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
template_data = open(os.path.join(TEST_VAR_DIR,
|
||||
'minimal.template')).read()
|
||||
resp = fakes.FakeHTTPResponse(
|
||||
@@ -464,7 +505,7 @@ class ShellTest(TestCase):
|
||||
self.assertRegexpMatches(show_text, r)
|
||||
|
||||
def test_template_show_hot(self):
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
resp_dict = {"heat_template_version": "2013-05-23",
|
||||
"parameters": {},
|
||||
"resources": {},
|
||||
@@ -490,7 +531,7 @@ class ShellTest(TestCase):
|
||||
self.assertRegexpMatches(show_text, r)
|
||||
|
||||
def test_create(self):
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(
|
||||
201,
|
||||
'Created',
|
||||
@@ -523,7 +564,7 @@ class ShellTest(TestCase):
|
||||
|
||||
def test_create_url(self):
|
||||
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(
|
||||
201,
|
||||
'Created',
|
||||
@@ -554,7 +595,7 @@ class ShellTest(TestCase):
|
||||
|
||||
def test_create_object(self):
|
||||
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
template_file = os.path.join(TEST_VAR_DIR, 'minimal.template')
|
||||
template_data = open(template_file).read()
|
||||
v1client.Client.raw_request(
|
||||
@@ -592,7 +633,7 @@ class ShellTest(TestCase):
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
def test_update(self):
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(
|
||||
202,
|
||||
'Accepted',
|
||||
@@ -624,7 +665,7 @@ class ShellTest(TestCase):
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
def test_delete(self):
|
||||
fakes.script_keystone_client()
|
||||
self._script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(
|
||||
204,
|
||||
'No Content',
|
||||
@@ -649,6 +690,25 @@ class ShellTest(TestCase):
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
|
||||
class ShellTestToken(ShellTestUserPass):
|
||||
|
||||
# Rerun all ShellTestUserPass test with token auth
|
||||
def setUp(self):
|
||||
self.token = 'a_token'
|
||||
super(ShellTestToken, self).setUp()
|
||||
|
||||
def _set_fake_env(self):
|
||||
fake_env = {
|
||||
'OS_AUTH_TOKEN': self.token,
|
||||
'OS_TENANT_ID': 'tenant_id',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.set_fake_env(fake_env)
|
||||
|
||||
def _script_keystone_client(self):
|
||||
fakes.script_keystone_client(token=self.token)
|
||||
|
||||
|
||||
class ShellEnvironmentTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user