Re-add keystone v3 auth with fixes
The first part of the commit is to re-propose the v3 auth changes. https://review.openstack.org/#/c/92728/ The second part of the commit is to address the unexpected keyword argument 'follow_redirect' and missing endpoint issue in certain code path such as 'heat event-list' or 'heat resource-metadata'. With the fix, follow_redirect argument is consumed by SessionClient. Heat endpoint is now passed from Heatclient shell to SessionClient. Unit tests were added and updated to cover the issue. Change-Id: I0cdf837c924afe9cbd8826bdca5dd611c183efeb Closes-Bug: #1349467 Closes-Bug: #1348297
This commit is contained in:
parent
f826bd612e
commit
9154f6dff8
|
@ -227,6 +227,12 @@ class HTTPClient(object):
|
|||
|
||||
def credentials_headers(self):
|
||||
creds = {}
|
||||
# NOTE(dhu): (shardy) When deferred_auth_method=password, Heat
|
||||
# encrypts and stores username/password. For Keystone v3, the
|
||||
# intent is to use trusts since SHARDY is working towards
|
||||
# deferred_auth_method=trusts as the default.
|
||||
# TODO(dhu): Make Keystone v3 work in Heat standalone mode. Maye
|
||||
# require X-Auth-User-Domain.
|
||||
if self.username:
|
||||
creds['X-Auth-User'] = self.username
|
||||
if self.password:
|
||||
|
@ -280,3 +286,76 @@ class HTTPClient(object):
|
|||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
|
||||
class SessionClient(HTTPClient):
|
||||
"""HTTP client based on Keystone client session."""
|
||||
|
||||
# NOTE(dhu): Will eventually move to a common session client.
|
||||
# https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
|
||||
def __init__(self, session, auth, endpoint, **kwargs):
|
||||
self.session = session
|
||||
self.auth = auth
|
||||
self.endpoint = endpoint
|
||||
|
||||
self.auth_url = kwargs.get('auth_url')
|
||||
self.region_name = kwargs.get('region_name')
|
||||
self.interface = kwargs.get('interface',
|
||||
kwargs.get('endpoint_type', 'public'))
|
||||
self.service_type = kwargs.get('service_type')
|
||||
|
||||
self.include_pass = kwargs.get('include_pass')
|
||||
self.username = kwargs.get('username')
|
||||
self.password = kwargs.get('password')
|
||||
# see if we can get the auth_url from auth plugin if one is not
|
||||
# provided from kwargs
|
||||
if not self.auth_url and hasattr(self.auth, 'auth_url'):
|
||||
self.auth_url = self.auth.auth_url
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('user_agent', USER_AGENT)
|
||||
kwargs.setdefault('auth', self.auth)
|
||||
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('interface', self.interface)
|
||||
endpoint_filter.setdefault('service_type', self.service_type)
|
||||
endpoint_filter.setdefault('region_name', self.region_name)
|
||||
|
||||
# TODO(gyee): what are these headers for?
|
||||
if self.auth_url:
|
||||
kwargs['headers'].setdefault('X-Auth-Url', self.auth_url)
|
||||
if self.region_name:
|
||||
kwargs['headers'].setdefault('X-Region-Name', self.region_name)
|
||||
if self.include_pass and 'X-Auth-Key' not in kwargs['headers']:
|
||||
kwargs['headers'].update(self.credentials_headers())
|
||||
|
||||
# Allow caller to specify not to follow redirects, in which case we
|
||||
# just return the redirect response. Useful for using stacks:lookup.
|
||||
follow_redirects = kwargs.pop('follow_redirects', True)
|
||||
|
||||
resp = self.session.request(url, method, redirect=follow_redirects,
|
||||
raise_exc=False, **kwargs)
|
||||
|
||||
if 400 <= resp.status_code < 600:
|
||||
raise exc.from_response(resp)
|
||||
elif resp.status_code in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location,
|
||||
# unless caller specified follow_redirects=False
|
||||
if follow_redirects:
|
||||
location = resp.headers.get('location')
|
||||
path = self.strip_endpoint(location)
|
||||
resp = self._http_request(path, method, **kwargs)
|
||||
elif resp.status_code == 300:
|
||||
raise exc.from_response(resp)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def _construct_http_client(*args, **kwargs):
|
||||
session = kwargs.pop('session', None)
|
||||
auth = kwargs.pop('auth', None)
|
||||
|
||||
if session:
|
||||
return SessionClient(session, auth, *args, **kwargs)
|
||||
else:
|
||||
return HTTPClient(*args, **kwargs)
|
||||
|
|
|
@ -18,15 +18,22 @@ from __future__ import print_function
|
|||
|
||||
import argparse
|
||||
import logging
|
||||
import six
|
||||
import sys
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from keystoneclient.auth.identity import v2 as v2_auth
|
||||
from keystoneclient.auth.identity import v3 as v3_auth
|
||||
from keystoneclient import discover
|
||||
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
|
||||
from keystoneclient import session as kssession
|
||||
|
||||
import heatclient
|
||||
from heatclient import client as heat_client
|
||||
from heatclient.common import utils
|
||||
from heatclient import exc
|
||||
from heatclient.openstack.common.gettextutils import _
|
||||
from heatclient.openstack.common import strutils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -34,6 +41,173 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class HeatShell(object):
|
||||
|
||||
def _append_global_identity_args(self, parser):
|
||||
# FIXME(gyee): these are global identity (Keystone) arguments which
|
||||
# should be consistent and shared by all service clients. Therefore,
|
||||
# they should be provided by python-keystoneclient. We will need to
|
||||
# refactor this code once this functionality is avaible in
|
||||
# python-keystoneclient.
|
||||
parser.add_argument('-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Explicitly allow heatclient 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('--os-cert',
|
||||
help='Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
'prepended with the private key.')
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--cert-file',
|
||||
dest='os_cert',
|
||||
help='DEPRECATED! Use --os-cert.')
|
||||
|
||||
parser.add_argument('--os-key',
|
||||
help='Path of client key to use in SSL '
|
||||
'connection. This option is not necessary '
|
||||
'if your key is prepended to your cert file.')
|
||||
|
||||
parser.add_argument('--key-file',
|
||||
dest='os_key',
|
||||
help='DEPRECATED! Use --os-key.')
|
||||
|
||||
parser.add_argument('--os-cacert',
|
||||
metavar='<ca-certificate-file>',
|
||||
dest='os_cacert',
|
||||
default=utils.env('OS_CACERT'),
|
||||
help='Path of CA TLS certificate(s) used to '
|
||||
'verify the remote server\'s certificate. '
|
||||
'Without this option glance looks for the '
|
||||
'default system CA certificates.')
|
||||
|
||||
parser.add_argument('--ca-file',
|
||||
dest='os_cacert',
|
||||
help='DEPRECATED! Use --os-cacert.')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME].')
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-user-id',
|
||||
default=utils.env('OS_USER_ID'),
|
||||
help='Defaults to env[OS_USER_ID].')
|
||||
|
||||
parser.add_argument('--os_user_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
default=utils.env('OS_USER_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_ID].')
|
||||
|
||||
parser.add_argument('--os_user_domain_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
default=utils.env('OS_USER_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_NAME].')
|
||||
|
||||
parser.add_argument('--os_user_domain_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-project-id',
|
||||
default=utils.env('OS_PROJECT_ID'),
|
||||
help='Another way to specify tenant ID. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-id. '
|
||||
'Defaults to env[OS_PROJECT_ID].')
|
||||
|
||||
parser.add_argument('--os_project_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-project-name',
|
||||
default=utils.env('OS_PROJECT_NAME'),
|
||||
help='Another way to specify tenant name. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-name. '
|
||||
'Defaults to env[OS_PROJECT_NAME].')
|
||||
|
||||
parser.add_argument('--os_project_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-project-domain-id',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
|
||||
|
||||
parser.add_argument('--os_project_domain_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||
|
||||
parser.add_argument('--os_project_domain_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD].')
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL].')
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME].')
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
default=utils.env('OS_SERVICE_TYPE'),
|
||||
help='Defaults to env[OS_SERVICE_TYPE].')
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE].')
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='heat',
|
||||
|
@ -63,92 +237,13 @@ class HeatShell(object):
|
|||
default=False, action="store_true",
|
||||
help="Print more verbose output.")
|
||||
|
||||
parser.add_argument('-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Explicitly allow the client 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('--os-cacert',
|
||||
metavar='<ca-certificate>',
|
||||
default=utils.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]')
|
||||
|
||||
parser.add_argument('--cert-file',
|
||||
help='Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
'prepended with the private key.')
|
||||
|
||||
parser.add_argument('--key-file',
|
||||
help='Path of client key to use in SSL connection.'
|
||||
'This option is not necessary if your key is'
|
||||
' prepended to your cert file.')
|
||||
|
||||
parser.add_argument('--ca-file',
|
||||
help='Path of CA SSL certificate(s) used to verify'
|
||||
' the remote server\'s certificate. Without this'
|
||||
' option the client looks'
|
||||
' for the default system CA certificates.')
|
||||
|
||||
parser.add_argument('--api-timeout',
|
||||
help='Number of seconds to wait for an '
|
||||
'API response, '
|
||||
'defaults to system socket timeout')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME].')
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD].')
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL].')
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME].')
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# os-no-client-auth tells heatclient to use token, instead of
|
||||
# env[OS_AUTH_URL]
|
||||
parser.add_argument('--os-no-client-auth',
|
||||
default=utils.env('OS_NO_CLIENT_AUTH'),
|
||||
action='store_true',
|
||||
|
@ -169,20 +264,6 @@ class HeatShell(object):
|
|||
parser.add_argument('--heat_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
default=utils.env('OS_SERVICE_TYPE'),
|
||||
help='Defaults to env[OS_SERVICE_TYPE].')
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE].')
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# This unused option should remain so that scripts that
|
||||
# use it do not break. It is suppressed so it will not
|
||||
# appear in the help.
|
||||
|
@ -196,6 +277,12 @@ class HeatShell(object):
|
|||
action='store_true',
|
||||
help='Send os-username and os-password to heat.')
|
||||
|
||||
# FIXME(gyee): this method should come from python-keystoneclient.
|
||||
# Will refactor this code once it is available.
|
||||
# https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
|
||||
|
||||
self._append_global_identity_args(parser)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
|
@ -241,45 +328,6 @@ class HeatShell(object):
|
|||
subparser.add_argument(*args, **kwargs)
|
||||
subparser.set_defaults(func=callback)
|
||||
|
||||
def _get_ksclient(self, **kwargs):
|
||||
"""Get an endpoint and auth token from Keystone.
|
||||
|
||||
:param username: name of user
|
||||
:param password: user's password
|
||||
: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'),
|
||||
'cacert': kwargs.get('cacert')}
|
||||
|
||||
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):
|
||||
"""Get an endpoint using the provided keystone client."""
|
||||
if kwargs.get('region_name'):
|
||||
return client.service_catalog.url_for(
|
||||
service_type=kwargs.get('service_type') or 'orchestration',
|
||||
attr='region',
|
||||
filter_value=kwargs.get('region_name'),
|
||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
||||
return client.service_catalog.url_for(
|
||||
service_type=kwargs.get('service_type') or 'orchestration',
|
||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
||||
|
||||
def _setup_logging(self, debug):
|
||||
log_lvl = logging.DEBUG if debug else logging.WARNING
|
||||
logging.basicConfig(
|
||||
|
@ -292,6 +340,120 @@ class HeatShell(object):
|
|||
if verbose:
|
||||
exc.verbose = 1
|
||||
|
||||
def _discover_auth_versions(self, session, auth_url):
|
||||
# discover the API versions the server is supporting base on the
|
||||
# given URL
|
||||
v2_auth_url = None
|
||||
v3_auth_url = None
|
||||
try:
|
||||
ks_discover = discover.Discover(session=session, auth_url=auth_url)
|
||||
v2_auth_url = ks_discover.url_for('2.0')
|
||||
v3_auth_url = ks_discover.url_for('3.0')
|
||||
except ks_exc.ClientException:
|
||||
# Identity service may not support discover API version.
|
||||
# Lets trying to figure out the API version from the original URL.
|
||||
url_parts = urlparse.urlparse(auth_url)
|
||||
(scheme, netloc, path, params, query, fragment) = url_parts
|
||||
path = path.lower()
|
||||
if path.startswith('/v3'):
|
||||
v3_auth_url = auth_url
|
||||
elif path.startswith('/v2'):
|
||||
v2_auth_url = auth_url
|
||||
else:
|
||||
# not enough information to determine the auth version
|
||||
msg = _('Unable to determine the Keystone version '
|
||||
'to authenticate with using the given '
|
||||
'auth_url. Identity service may not support API '
|
||||
'version discovery. Please provide a versioned '
|
||||
'auth_url instead.')
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
return (v2_auth_url, v3_auth_url)
|
||||
|
||||
def _get_keystone_session(self, **kwargs):
|
||||
# first create a Keystone session
|
||||
cacert = kwargs.pop('cacert', None)
|
||||
cert = kwargs.pop('cert', None)
|
||||
key = kwargs.pop('key', None)
|
||||
insecure = kwargs.pop('insecure', False)
|
||||
timeout = kwargs.pop('timeout', None)
|
||||
verify = kwargs.pop('verify', None)
|
||||
|
||||
# FIXME(gyee): this code should come from keystoneclient
|
||||
if verify is None:
|
||||
if insecure:
|
||||
verify = False
|
||||
else:
|
||||
# TODO(gyee): should we do
|
||||
# heatclient.common.http.get_system_ca_fle()?
|
||||
verify = cacert or True
|
||||
if cert and key:
|
||||
# passing cert and key together is deprecated in favour of the
|
||||
# requests lib form of having the cert and key as a tuple
|
||||
cert = (cert, key)
|
||||
|
||||
return kssession.Session(verify=verify, cert=cert, timeout=timeout)
|
||||
|
||||
def _get_keystone_v3_auth(self, v3_auth_url, **kwargs):
|
||||
auth_token = kwargs.pop('auth_token', None)
|
||||
if auth_token:
|
||||
return v3_auth.Token(v3_auth_url, auth_token)
|
||||
else:
|
||||
return v3_auth.Password(v3_auth_url, **kwargs)
|
||||
|
||||
def _get_keystone_v2_auth(self, v2_auth_url, **kwargs):
|
||||
auth_token = kwargs.pop('auth_token', None)
|
||||
tenant_id = kwargs.pop('project_id', None)
|
||||
tenant_name = kwargs.pop('project_name', None)
|
||||
if auth_token:
|
||||
return v2_auth.Token(v2_auth_url, auth_token,
|
||||
tenant_id=tenant_id,
|
||||
tenant_name=tenant_name)
|
||||
else:
|
||||
return v2_auth.Password(v2_auth_url,
|
||||
username=kwargs.pop('username', None),
|
||||
password=kwargs.pop('password', None),
|
||||
tenant_id=tenant_id,
|
||||
tenant_name=tenant_name)
|
||||
|
||||
def _get_keystone_auth(self, session, auth_url, **kwargs):
|
||||
# FIXME(dhu): this code should come from keystoneclient
|
||||
|
||||
# discover the supported keystone versions using the given url
|
||||
(v2_auth_url, v3_auth_url) = self._discover_auth_versions(
|
||||
session=session,
|
||||
auth_url=auth_url)
|
||||
|
||||
# Determine which authentication plugin to use. First inspect the
|
||||
# auth_url to see the supported version. If both v3 and v2 are
|
||||
# supported, then use the highest version if possible.
|
||||
auth = None
|
||||
if v3_auth_url and v2_auth_url:
|
||||
user_domain_name = kwargs.get('user_domain_name', None)
|
||||
user_domain_id = kwargs.get('user_domain_id', None)
|
||||
project_domain_name = kwargs.get('project_domain_name', None)
|
||||
project_domain_id = kwargs.get('project_domain_id', None)
|
||||
|
||||
# support both v2 and v3 auth. Use v3 if domain information is
|
||||
# provided.
|
||||
if (user_domain_name or user_domain_id or project_domain_name or
|
||||
project_domain_id):
|
||||
auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
|
||||
else:
|
||||
auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
|
||||
elif v3_auth_url:
|
||||
# support only v3
|
||||
auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
|
||||
elif v2_auth_url:
|
||||
# support only v2
|
||||
auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
|
||||
else:
|
||||
raise exc.CommandError('Unable to determine the Keystone version '
|
||||
'to authenticate with using the given '
|
||||
'auth_url.')
|
||||
|
||||
return auth
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
|
@ -340,13 +502,21 @@ class HeatShell(object):
|
|||
" via either --heat-url or"
|
||||
" env[HEAT_URL]")
|
||||
else:
|
||||
# Tenant name or ID is needed to make keystoneclient retrieve a
|
||||
# service catalog, it's not required if os_no_client_auth is
|
||||
# specified, neither is the auth URL
|
||||
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]")
|
||||
# Tenant/project name or ID is needed to make keystoneclient
|
||||
# retrieve a service catalog, it's not required if
|
||||
# os_no_client_auth is specified, neither is the auth URL
|
||||
|
||||
if not (args.os_tenant_id or args.os_tenant_name or
|
||||
args.os_project_id or args.os_project_name):
|
||||
raise exc.CommandError("You must provide a tenant id via"
|
||||
" either --os-tenant-id or"
|
||||
" env[OS_TENANT_ID] or a tenant name"
|
||||
" via either --os-tenant-name or"
|
||||
" env[OS_TENANT_NAME] or a project id"
|
||||
" via either --os-project-id or"
|
||||
" env[OS_PROJECT_ID] or a project"
|
||||
" name via either --os-project-name or"
|
||||
" env[OS_PROJECT_NAME]")
|
||||
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError("You must provide an auth url via"
|
||||
|
@ -354,46 +524,65 @@ class HeatShell(object):
|
|||
" env[OS_AUTH_URL]")
|
||||
|
||||
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,
|
||||
'service_type': args.os_service_type,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'insecure': args.insecure,
|
||||
'cacert': args.os_cacert,
|
||||
'include_pass': args.include_password
|
||||
'cert': args.os_cert,
|
||||
'key': args.os_key,
|
||||
'timeout': args.api_timeout
|
||||
}
|
||||
|
||||
endpoint = args.heat_url
|
||||
|
||||
if not args.os_no_client_auth:
|
||||
_ksclient = self._get_ksclient(**kwargs)
|
||||
token = args.os_auth_token or _ksclient.auth_token
|
||||
|
||||
service_type = args.os_service_type or 'orchestration'
|
||||
if args.os_no_client_auth:
|
||||
# Do not use session since no_client_auth means using heat to
|
||||
# to authenticate
|
||||
kwargs = {
|
||||
'token': token,
|
||||
'insecure': args.insecure,
|
||||
'ca_file': args.ca_file,
|
||||
'cert_file': args.cert_file,
|
||||
'key_file': args.key_file,
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'token': args.os_auth_token,
|
||||
'include_pass': args.include_password,
|
||||
'insecure': args.insecure,
|
||||
'timeout': args.api_timeout
|
||||
}
|
||||
else:
|
||||
keystone_session = self._get_keystone_session(**kwargs)
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
kwargs = {
|
||||
'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,
|
||||
}
|
||||
keystone_auth = self._get_keystone_auth(keystone_session,
|
||||
args.os_auth_url,
|
||||
**kwargs)
|
||||
if not endpoint:
|
||||
svc_type = service_type
|
||||
region_name = args.os_region_name
|
||||
endpoint = keystone_auth.get_endpoint(keystone_session,
|
||||
service_type=svc_type,
|
||||
region_name=region_name)
|
||||
|
||||
endpoint_type = args.os_endpoint_type or 'publicURL'
|
||||
kwargs = {
|
||||
'auth_url': args.os_auth_url,
|
||||
'session': keystone_session,
|
||||
'auth': keystone_auth,
|
||||
'service_type': service_type,
|
||||
'endpoint_type': endpoint_type,
|
||||
'region_name': args.os_region_name,
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'include_pass': args.include_password
|
||||
}
|
||||
|
||||
if args.os_region_name:
|
||||
kwargs['region_name'] = args.os_region_name
|
||||
|
||||
if not endpoint:
|
||||
endpoint = self._get_endpoint(_ksclient, **kwargs)
|
||||
|
||||
if args.api_timeout:
|
||||
kwargs['timeout'] = args.api_timeout
|
||||
|
||||
client = heat_client.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
args.func(client, args)
|
||||
|
|
|
@ -14,24 +14,6 @@
|
|||
from heatclient.common import http
|
||||
from heatclient import exc
|
||||
from heatclient.openstack.common import jsonutils
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
|
||||
|
||||
def script_keystone_client(token=None):
|
||||
if token:
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
cacert=None,
|
||||
tenant_id='tenant_id',
|
||||
token=token).AndReturn(FakeKeystone(token))
|
||||
else:
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
cacert=None,
|
||||
password='password',
|
||||
tenant_name='tenant_name',
|
||||
username='username').AndReturn(FakeKeystone(
|
||||
'abcd1234'))
|
||||
|
||||
|
||||
def script_heat_list(url=None):
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneclient.fixture import v3 as ks_v3_fixture
|
||||
import uuid
|
||||
|
||||
from heatclient.openstack.common import jsonutils
|
||||
|
||||
# these are copied from python-keystoneclient tests
|
||||
BASE_HOST = 'http://keystone.example.com'
|
||||
BASE_URL = "%s:5000/" % BASE_HOST
|
||||
UPDATED = '2013-03-06T00:00:00Z'
|
||||
|
||||
V2_URL = "%sv2.0" % BASE_URL
|
||||
V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/'
|
||||
'openstack-identity-service/2.0/content/',
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html'}
|
||||
V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident'
|
||||
'ity-service/2.0/identity-dev-guide-2.0.pdf',
|
||||
'rel': 'describedby',
|
||||
'type': 'application/pdf'}
|
||||
|
||||
V2_VERSION = {'id': 'v2.0',
|
||||
'links': [{'href': V2_URL, 'rel': 'self'},
|
||||
V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF],
|
||||
'status': 'stable',
|
||||
'updated': UPDATED}
|
||||
|
||||
V3_URL = "%sv3" % BASE_URL
|
||||
V3_MEDIA_TYPES = [{'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.identity-v3+json'},
|
||||
{'base': 'application/xml',
|
||||
'type': 'application/vnd.openstack.identity-v3+xml'}]
|
||||
|
||||
V3_VERSION = {'id': 'v3.0',
|
||||
'links': [{'href': V3_URL, 'rel': 'self'}],
|
||||
'media-types': V3_MEDIA_TYPES,
|
||||
'status': 'stable',
|
||||
'updated': UPDATED}
|
||||
|
||||
TOKENID = uuid.uuid4().hex
|
||||
|
||||
|
||||
def _create_version_list(versions):
|
||||
return jsonutils.dumps({'versions': {'values': versions}})
|
||||
|
||||
|
||||
def _create_single_version(version):
|
||||
return jsonutils.dumps({'version': version})
|
||||
|
||||
|
||||
V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
|
||||
V2_VERSION_LIST = _create_version_list([V2_VERSION])
|
||||
|
||||
V3_VERSION_ENTRY = _create_single_version(V3_VERSION)
|
||||
V2_VERSION_ENTRY = _create_single_version(V2_VERSION)
|
||||
|
||||
HEAT_ENDPOINT = 'http://www.heat.com/v1'
|
||||
|
||||
|
||||
def keystone_request_callback(request, uri, headers):
|
||||
response_headers = {"content-type": "application/json"}
|
||||
token_id = TOKENID
|
||||
if uri == BASE_URL:
|
||||
return (200, headers, V3_VERSION_LIST)
|
||||
elif uri == BASE_URL + "/v2.0":
|
||||
v2_token = ks_v2_fixture.Token(token_id)
|
||||
return (200, response_headers, jsonutils.dumps(v2_token))
|
||||
elif uri == BASE_URL + "/v3":
|
||||
v3_token = ks_v3_fixture.Token()
|
||||
response_headers["X-Subject-Token"] = token_id
|
||||
return (201, response_headers, jsonutils.dumps(v3_token))
|
File diff suppressed because it is too large
Load Diff
|
@ -36,7 +36,7 @@ class Client(object):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Heat v1 API."""
|
||||
self.http_client = http.HTTPClient(*args, **kwargs)
|
||||
self.http_client = http._construct_http_client(*args, **kwargs)
|
||||
self.stacks = stacks.StackManager(self.http_client)
|
||||
self.resources = resources.ResourceManager(self.http_client)
|
||||
self.resource_types = resource_types.ResourceTypeManager(
|
||||
|
|
|
@ -3,6 +3,7 @@ discover
|
|||
fixtures>=0.3.14
|
||||
# Hacking already pins down pep8, pyflakes and flake8
|
||||
hacking>=0.8.0,<0.9
|
||||
httpretty>=0.8.0,!=0.8.1,!=0.8.2,!=0.8.3
|
||||
mock>=1.0
|
||||
mox3>=0.7.0
|
||||
oslosphinx>=2.2.0.0a2
|
||||
|
|
Loading…
Reference in New Issue