Merge "Add Support for Keystone V3 CLI"
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from keystoneclient import adapter
|
||||
from keystoneclient.v2_0 import client as keystone_client_v2
|
||||
from keystoneclient.v3 import client as keystone_client_v3
|
||||
|
||||
@@ -33,26 +34,38 @@ class Client(object):
|
||||
def __init__(self, username=None, api_key=None, project_id=None,
|
||||
project_name=None, auth_url=None, sahara_url=None,
|
||||
endpoint_type='publicURL', service_type='data_processing',
|
||||
input_auth_token=None):
|
||||
service_name=None, region_name=None,
|
||||
input_auth_token=None, session=None, auth=None):
|
||||
|
||||
keystone = None
|
||||
sahara_catalog_url = sahara_url
|
||||
|
||||
if not input_auth_token:
|
||||
keystone = self.get_keystone_client(username=username,
|
||||
api_key=api_key,
|
||||
auth_url=auth_url,
|
||||
project_id=project_id,
|
||||
project_name=project_name)
|
||||
input_auth_token = keystone.auth_token
|
||||
|
||||
if session:
|
||||
keystone = adapter.LegacyJsonAdapter(
|
||||
session=session,
|
||||
auth=auth,
|
||||
interface=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
region_name=region_name)
|
||||
input_auth_token = keystone.session.get_token(auth)
|
||||
sahara_catalog_url = keystone.session.get_endpoint(
|
||||
auth, interface=endpoint_type, service_type=service_type)
|
||||
else:
|
||||
keystone = self.get_keystone_client(
|
||||
username=username,
|
||||
api_key=api_key,
|
||||
auth_url=auth_url,
|
||||
project_id=project_id,
|
||||
project_name=project_name)
|
||||
input_auth_token = keystone.auth_token
|
||||
|
||||
if not input_auth_token:
|
||||
raise RuntimeError("Not Authorized")
|
||||
|
||||
sahara_catalog_url = sahara_url
|
||||
if not sahara_url:
|
||||
keystone = self.get_keystone_client(username=username,
|
||||
api_key=api_key,
|
||||
auth_url=auth_url,
|
||||
token=input_auth_token,
|
||||
project_id=project_id,
|
||||
project_name=project_name)
|
||||
if not sahara_catalog_url:
|
||||
catalog = keystone.service_catalog.get_endpoints(service_type)
|
||||
if service_type not in catalog:
|
||||
service_type = service_type.replace('-', '_')
|
||||
@@ -86,10 +99,12 @@ class Client(object):
|
||||
def get_keystone_client(self, username=None, api_key=None, auth_url=None,
|
||||
token=None, project_id=None, project_name=None):
|
||||
if not auth_url:
|
||||
raise RuntimeError("No auth url specified")
|
||||
imported_client = (keystone_client_v2 if "v2.0" in auth_url
|
||||
else keystone_client_v3)
|
||||
raise RuntimeError("No auth url specified")
|
||||
|
||||
if not getattr(self, "keystone_client", None):
|
||||
imported_client = (keystone_client_v2 if "v2.0" in auth_url
|
||||
else keystone_client_v3)
|
||||
|
||||
self.keystone_client = imported_client.Client(
|
||||
username=username,
|
||||
password=api_key,
|
||||
|
||||
@@ -47,6 +47,11 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from keystoneclient.auth.identity.generic import password
|
||||
from keystoneclient.auth.identity.generic import token
|
||||
from keystoneclient.auth.identity import v3 as identity
|
||||
from keystoneclient import session
|
||||
|
||||
from saharaclient.api import client
|
||||
from saharaclient.api import shell as shell_api
|
||||
from saharaclient.openstack.common.apiclient import auth
|
||||
@@ -251,6 +256,7 @@ class OpenStackSaharaShell(object):
|
||||
help="Use the auth token cache. Defaults to False "
|
||||
"if env[OS_CACHE] is not set.")
|
||||
|
||||
|
||||
# TODO(mattf) - add get_timings support to Client
|
||||
# parser.add_argument('--timings',
|
||||
# default=False,
|
||||
@@ -264,11 +270,6 @@ class OpenStackSaharaShell(object):
|
||||
# type=positive_non_zero_float,
|
||||
# help="Set HTTP call timeout (in seconds)")
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
metavar='<auth-tenant-id>',
|
||||
default=cliutils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
|
||||
# NA
|
||||
# parser.add_argument('--os-region-name',
|
||||
# metavar='<region-name>',
|
||||
@@ -324,22 +325,6 @@ class OpenStackSaharaShell(object):
|
||||
parser.add_argument('--sahara_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-cacert',
|
||||
metavar='<ca-certificate>',
|
||||
default=cliutils.env('OS_CACERT', default=None),
|
||||
help='Specify a CA bundle file to use in '
|
||||
'verifying a TLS (https) server certificate. '
|
||||
'Defaults to env[OS_CACERT].')
|
||||
|
||||
# NA
|
||||
# parser.add_argument('--insecure',
|
||||
# default=utils.env('NOVACLIENT_INSECURE', default=False),
|
||||
# action='store_true',
|
||||
# help="Explicitly allow novaclient to perform \"insecure\" "
|
||||
# "SSL (https) requests. The server's certificate will "
|
||||
# "not be verified against any certificate authorities. "
|
||||
# "This option should be used with caution.")
|
||||
|
||||
parser.add_argument('--bypass-url',
|
||||
metavar='<bypass-url>',
|
||||
default=cliutils.env('BYPASS_URL', default=None),
|
||||
@@ -349,8 +334,25 @@ class OpenStackSaharaShell(object):
|
||||
parser.add_argument('--bypass_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# The auth-system-plugins might require some extra options
|
||||
auth.load_auth_system_opts(parser)
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=cliutils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=cliutils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
|
||||
parser.add_argument('--os-auth-system',
|
||||
default=cliutils.env('OS_AUTH_SYSTEM'),
|
||||
help='Defaults to env[OS_AUTH_SYSTEM].')
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=cliutils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
|
||||
# Use Keystoneclient API to parse authentication arguments
|
||||
session.Session.register_cli_options(parser)
|
||||
identity.Password.register_argparse_arguments(parser)
|
||||
|
||||
return parser
|
||||
|
||||
@@ -418,12 +420,27 @@ class OpenStackSaharaShell(object):
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format=streamformat)
|
||||
|
||||
def _get_keystone_auth(self, session, auth_url, **kwargs):
|
||||
auth_token = kwargs.pop('auth_token', None)
|
||||
if auth_token:
|
||||
return token.Token(auth_url, auth_token, **kwargs)
|
||||
else:
|
||||
return password.Password(
|
||||
auth_url,
|
||||
username=kwargs.pop('username'),
|
||||
user_id=kwargs.pop('user_id'),
|
||||
password=kwargs.pop('password'),
|
||||
user_domain_id=kwargs.pop('user_domain_id'),
|
||||
user_domain_name=kwargs.pop('user_domain_name'),
|
||||
**kwargs)
|
||||
|
||||
def main(self, argv):
|
||||
|
||||
# Parse args once to find version and debug settings
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self.setup_debugging(options.debug)
|
||||
self.options = options
|
||||
|
||||
# NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse
|
||||
# thinking usage-list --end is ambiguous; but it
|
||||
@@ -502,12 +519,6 @@ class OpenStackSaharaShell(object):
|
||||
"via either --os-username or "
|
||||
"env[OS_USERNAME]")
|
||||
|
||||
if not os_tenant_name and not os_tenant_id:
|
||||
raise exc.CommandError("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]")
|
||||
|
||||
if not os_auth_url:
|
||||
if os_auth_system and os_auth_system != 'keystone':
|
||||
os_auth_url = auth_plugin.get_auth_url()
|
||||
@@ -574,21 +585,63 @@ class OpenStackSaharaShell(object):
|
||||
# self.cs.client.password = os_password
|
||||
# self.cs.client.keyring_saver = helper
|
||||
|
||||
# NA
|
||||
# try:
|
||||
# if not utils.isunauthenticated(args.func):
|
||||
# self.cs.authenticate()
|
||||
# except exc.Unauthorized:
|
||||
# raise exc.CommandError("Invalid OpenStack Sahara credentials.")
|
||||
# except exc.AuthorizationFailure:
|
||||
# raise exc.CommandError("Unable to authorize user")
|
||||
# V3 stuff
|
||||
project_info_provided = (self.options.os_tenant_name or
|
||||
self.options.os_tenant_id or
|
||||
(self.options.os_project_name and
|
||||
(self.options.os_project_domain_name or
|
||||
self.options.os_project_domain_id)) or
|
||||
self.options.os_project_id)
|
||||
|
||||
if (not project_info_provided):
|
||||
raise exc.CommandError(
|
||||
("You must provide a tenant_name, tenant_id, "
|
||||
"project_id or project_name (with "
|
||||
"project_domain_name or project_domain_id) via "
|
||||
" --os-tenant-name (env[OS_TENANT_NAME]),"
|
||||
" --os-tenant-id (env[OS_TENANT_ID]),"
|
||||
" --os-project-id (env[OS_PROJECT_ID])"
|
||||
" --os-project-name (env[OS_PROJECT_NAME]),"
|
||||
" --os-project-domain-id "
|
||||
"(env[OS_PROJECT_DOMAIN_ID])"
|
||||
" --os-project-domain-name "
|
||||
"(env[OS_PROJECT_DOMAIN_NAME])"))
|
||||
|
||||
if not os_auth_url:
|
||||
raise exc.CommandError(
|
||||
"You must provide an auth url "
|
||||
"via either --os-auth-url or env[OS_AUTH_URL]")
|
||||
|
||||
keystone_session = None
|
||||
keystone_auth = None
|
||||
if not auth_plugin:
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
|
||||
keystone_session = (session.Session.
|
||||
load_from_cli_options(args))
|
||||
keystone_auth = self._get_keystone_auth(
|
||||
keystone_session,
|
||||
args.os_auth_url,
|
||||
username=args.os_username,
|
||||
user_id=args.os_user_id,
|
||||
user_domain_id=args.os_user_domain_id,
|
||||
user_domain_name=args.os_user_domain_name,
|
||||
password=args.os_password,
|
||||
auth_token=args.os_auth_token,
|
||||
project_id=project_id,
|
||||
project_name=project_name,
|
||||
project_domain_id=args.os_project_domain_id,
|
||||
project_domain_name=args.os_project_domain_name)
|
||||
|
||||
self.cs = client.Client(username=os_username,
|
||||
api_key=os_password,
|
||||
project_id=os_tenant_id,
|
||||
project_name=os_tenant_name,
|
||||
auth_url=os_auth_url,
|
||||
sahara_url=bypass_url)
|
||||
sahara_url=bypass_url,
|
||||
session=keystone_session,
|
||||
auth=keystone_auth)
|
||||
|
||||
args.func(self.cs, args)
|
||||
|
||||
|
||||
@@ -170,9 +170,19 @@ class ShellTest(utils.TestCase):
|
||||
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]',)
|
||||
required = (
|
||||
'You must provide a tenant_name, tenant_id, '
|
||||
'project_id or project_name (with '
|
||||
'project_domain_name or project_domain_id) via '
|
||||
' --os-tenant-name (env[OS_TENANT_NAME]),'
|
||||
' --os-tenant-id (env[OS_TENANT_ID]),'
|
||||
' --os-project-id (env[OS_PROJECT_ID])'
|
||||
' --os-project-name (env[OS_PROJECT_NAME]),'
|
||||
' --os-project-domain-id '
|
||||
'(env[OS_PROJECT_DOMAIN_ID])'
|
||||
' --os-project-domain-name '
|
||||
'(env[OS_PROJECT_DOMAIN_NAME])',
|
||||
)
|
||||
self.make_env(exclude='OS_TENANT_NAME')
|
||||
try:
|
||||
self.shell('plugin-list')
|
||||
@@ -182,9 +192,19 @@ class ShellTest(utils.TestCase):
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_tenant_id(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]',)
|
||||
required = (
|
||||
'You must provide a tenant_name, tenant_id, '
|
||||
'project_id or project_name (with '
|
||||
'project_domain_name or project_domain_id) via '
|
||||
' --os-tenant-name (env[OS_TENANT_NAME]),'
|
||||
' --os-tenant-id (env[OS_TENANT_ID]),'
|
||||
' --os-project-id (env[OS_PROJECT_ID])'
|
||||
' --os-project-name (env[OS_PROJECT_NAME]),'
|
||||
' --os-project-domain-id '
|
||||
'(env[OS_PROJECT_DOMAIN_ID])'
|
||||
' --os-project-domain-name '
|
||||
'(env[OS_PROJECT_DOMAIN_NAME])',
|
||||
)
|
||||
self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2)
|
||||
try:
|
||||
self.shell('plugin-list')
|
||||
@@ -281,3 +301,67 @@ class ShellTest(utils.TestCase):
|
||||
self.make_env()
|
||||
stdout, stderr = self.shell('image-list')
|
||||
self.assertEqual(ex, (stdout + stderr))
|
||||
|
||||
|
||||
class ShellTestKeystoneV3(ShellTest):
|
||||
|
||||
FAKE_V3_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_NAME': 'project_name',
|
||||
'OS_PROJECT_DOMAIN_NAME': 'project_domain_name',
|
||||
'OS_USER_DOMAIN_NAME': 'user_domain_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v3'}
|
||||
|
||||
version_id = u'v3'
|
||||
links = [{u'href': u'http://no.where/v3', u'rel': u'self'}]
|
||||
|
||||
def make_env(self, exclude=None, fake_env=FAKE_V3_ENV):
|
||||
if 'OS_AUTH_URL' in fake_env:
|
||||
fake_env.update({'OS_AUTH_URL': 'http://no.where/v3'})
|
||||
env = dict((k, v) for k, v in fake_env.items() if k != exclude)
|
||||
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
|
||||
|
||||
def test_no_tenant_name(self):
|
||||
# In V3, tenant_name = project_name
|
||||
required = (
|
||||
'You must provide a tenant_name, tenant_id, '
|
||||
'project_id or project_name (with '
|
||||
'project_domain_name or project_domain_id) via '
|
||||
' --os-tenant-name (env[OS_TENANT_NAME]),'
|
||||
' --os-tenant-id (env[OS_TENANT_ID]),'
|
||||
' --os-project-id (env[OS_PROJECT_ID])'
|
||||
' --os-project-name (env[OS_PROJECT_NAME]),'
|
||||
' --os-project-domain-id '
|
||||
'(env[OS_PROJECT_DOMAIN_ID])'
|
||||
' --os-project-domain-name '
|
||||
'(env[OS_PROJECT_DOMAIN_NAME])',
|
||||
)
|
||||
self.make_env(exclude='OS_PROJECT_NAME')
|
||||
try:
|
||||
self.shell('plugin-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args)
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_job_list(self):
|
||||
expected = '\n'.join([
|
||||
'+----+------------+--------+',
|
||||
'| id | cluster_id | status |',
|
||||
'+----+------------+--------+',
|
||||
'+----+------------+--------+',
|
||||
''
|
||||
])
|
||||
|
||||
mock_session_class_name = 'keystoneclient.adapter.LegacyJsonAdapter'
|
||||
mock_job_executions_class_name = (
|
||||
'saharaclient.api.job_executions.JobExecutionsManager')
|
||||
|
||||
with mock.patch(mock_session_class_name) as mock_session:
|
||||
with mock.patch(mock_job_executions_class_name):
|
||||
ms = mock_session.return_value
|
||||
ms.session.get_endpoint.return_value = 'http://no.where'
|
||||
|
||||
self.make_env()
|
||||
stdout, stderr = self.shell('job-list')
|
||||
self.assertEqual((stdout + stderr), expected)
|
||||
|
||||
Reference in New Issue
Block a user