857b532d21
Older versions of os-client-config pop OS_CLOUD from the environment
when make_client is called. This has been fixed in [1], but to
support older versions as well, let's cache it on import so even if
someone messes with os.environ later we still have the right value.
1: 990cfa3ce2
206 lines
8.0 KiB
Python
206 lines
8.0 KiB
Python
# Copyright 2017 Red Hat Inc.
|
|
#
|
|
# 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.
|
|
|
|
import os
|
|
import sys
|
|
|
|
from keystoneclient.v2_0 import client as keystone_client
|
|
from keystoneclient.v3 import client as keystone_v3_client
|
|
|
|
|
|
# Older versions of os-client-config pop this from the environment when
|
|
# make_client is called. Cache it on import so we know what the original
|
|
# value was, regardless of any funny business that happens later.
|
|
OS_CLOUD = os.environ.get('OS_CLOUD')
|
|
|
|
|
|
def _validate_auth_parameters(username, password, tenant, auth_url,
|
|
project, user_domain, project_domain):
|
|
"""Validate that the necessary auth parameters are set
|
|
|
|
Depending on the version of keystone in use, certain parameters are
|
|
required for successful keystone authentication. If the combination
|
|
passed to this function is not valid, it will print an error message and
|
|
exit with a return code of 1 immediately.
|
|
"""
|
|
if '/v3' not in auth_url:
|
|
if not username or not password or not tenant or not auth_url:
|
|
print('Source an appropriate rc file first')
|
|
sys.exit(1)
|
|
else:
|
|
if (not username or not password or not auth_url or not project or
|
|
not user_domain or not project_domain):
|
|
print('Source an appropriate rc file first')
|
|
sys.exit(1)
|
|
|
|
def _create_auth_parameters():
|
|
"""Read keystone auth parameters from appropriate source
|
|
|
|
If the environment variable OS_CLOUD is set, read the auth information
|
|
from os_client_config. Otherwise, read it from environment variables.
|
|
When reading from the environment, also validate that all of the required
|
|
values are set.
|
|
|
|
:returns: A dict containing the following keys: os_user, os_password,
|
|
os_tenant, os_auth_url, os_project, os_user_domain,
|
|
os_project_domain.
|
|
"""
|
|
if OS_CLOUD:
|
|
import os_client_config
|
|
config = os_client_config.OpenStackConfig().get_one_cloud(OS_CLOUD)
|
|
auth = config.config['auth']
|
|
username = auth['username']
|
|
password = auth['password']
|
|
# os_client_config seems to always call this project_name
|
|
tenant = auth['project_name']
|
|
auth_url = auth['auth_url']
|
|
project = auth['project_name']
|
|
user_domain = (auth.get('user_domain_name') or
|
|
auth.get('user_domain_id', ''))
|
|
project_domain = (auth.get('project_domain_name') or
|
|
auth.get('project_domain_id', ''))
|
|
|
|
else:
|
|
username = os.environ.get('OS_USERNAME')
|
|
password = os.environ.get('OS_PASSWORD')
|
|
tenant = os.environ.get('OS_TENANT_NAME', '')
|
|
auth_url = os.environ.get('OS_AUTH_URL', '')
|
|
project = os.environ.get('OS_PROJECT_NAME', '')
|
|
user_domain = (os.environ.get('OS_USER_DOMAIN_ID') or
|
|
os.environ.get('OS_USER_DOMAIN_NAME', ''))
|
|
project_domain = (os.environ.get('OS_PROJECT_DOMAIN_ID') or
|
|
os.environ.get('OS_PROJECT_DOMAIN_NAME', ''))
|
|
_validate_auth_parameters(username, password, tenant, auth_url,
|
|
project, user_domain, project_domain)
|
|
return {'os_user': username,
|
|
'os_password': password,
|
|
'os_tenant': tenant,
|
|
'os_auth_url': auth_url,
|
|
'os_project': project,
|
|
'os_user_domain': user_domain,
|
|
'os_project_domain': project_domain,
|
|
}
|
|
|
|
|
|
def _get_keystone_session(auth_data):
|
|
"""Get a new keystone session object
|
|
|
|
:param auth_data: Dict of authentication parameters as returned from
|
|
_create_auth_parameters.
|
|
|
|
:returns: keystoneclient Session
|
|
"""
|
|
username = auth_data['os_user']
|
|
password = auth_data['os_password']
|
|
tenant = auth_data['os_tenant']
|
|
auth_url = auth_data['os_auth_url']
|
|
project = auth_data['os_project']
|
|
user_domain = auth_data['os_user_domain']
|
|
project_domain = auth_data['os_project_domain']
|
|
from keystoneauth1.identity import v3
|
|
from keystoneauth1 import session
|
|
password_auth = v3.Password(auth_url=auth_url,
|
|
username=username,
|
|
password=password,
|
|
project_name=project,
|
|
user_domain_name=user_domain,
|
|
project_domain_name=project_domain)
|
|
return session.Session(auth=password_auth)
|
|
|
|
|
|
def _get_keystone_client(auth_data):
|
|
"""Get an instance of keystoneclient.Client
|
|
|
|
Abstracts away the version-specific logic of getting a new instance of
|
|
the keystone client.
|
|
|
|
:param auth_data: Dict of authentication parameters as returned from
|
|
_create_auth_parameters.
|
|
|
|
:returns: A new keystoneclient Client instance
|
|
"""
|
|
username = auth_data['os_user']
|
|
password = auth_data['os_password']
|
|
tenant = auth_data['os_tenant']
|
|
auth_url = auth_data['os_auth_url']
|
|
if '/v3' not in auth_url:
|
|
return keystone_client.Client(username=username, password=password,
|
|
tenant_name=tenant, auth_url=auth_url)
|
|
else:
|
|
sess = _get_keystone_session(auth_data)
|
|
return keystone_v3_client.Client(session=sess)
|
|
|
|
|
|
def _get_keystone_token():
|
|
"""Get a raw keystone token
|
|
|
|
This is a wrapper around the keystoneclient
|
|
get_raw_token_from_identity_service that handles both keystone v2 and v3.
|
|
|
|
:returns: Keystone token data structure
|
|
"""
|
|
auth_data = _create_auth_parameters()
|
|
username = auth_data['os_user']
|
|
password = auth_data['os_password']
|
|
tenant = auth_data['os_tenant']
|
|
auth_url = auth_data['os_auth_url']
|
|
project = auth_data['os_project']
|
|
user_domain = auth_data['os_user_domain']
|
|
project_domain = auth_data['os_project_domain']
|
|
kclient = _get_keystone_client(auth_data)
|
|
if '/v3' not in auth_url:
|
|
return kclient.get_raw_token_from_identity_service(username=username,
|
|
password=password,
|
|
tenant_name=tenant,
|
|
auth_url=auth_url)
|
|
else:
|
|
return kclient.get_raw_token_from_identity_service(
|
|
username=username,
|
|
password=password,
|
|
project_name=project,
|
|
auth_url=auth_url,
|
|
user_domain_name=user_domain,
|
|
project_domain_name=project_domain)
|
|
|
|
|
|
def _get_token_and_endpoint(name):
|
|
"""Return a token id and endpoint url for the specified service
|
|
|
|
:param name: The name of the service. heat, glance, etc.
|
|
:returns: A tuple of (token_id, service_endpoint)
|
|
"""
|
|
auth_data = _create_auth_parameters()
|
|
auth_url = auth_data['os_auth_url']
|
|
# Get token for service to use
|
|
if '/v3' not in auth_url:
|
|
token_data = _get_keystone_token()
|
|
token_id = token_data['token']['id']
|
|
catalog_key = 'serviceCatalog'
|
|
else:
|
|
token_data = _get_keystone_token()
|
|
token_id = token_data['auth_token']
|
|
catalog_key = 'catalog'
|
|
|
|
# Get service endpoint
|
|
for endpoint in token_data[catalog_key]:
|
|
if endpoint['name'] == name:
|
|
try:
|
|
# TODO: What if there's more than one endpoint?
|
|
service_endpoint = endpoint['endpoints'][0]['publicURL']
|
|
except KeyError:
|
|
# Keystone v3 endpoint data looks different
|
|
service_endpoint = [e for e in endpoint['endpoints']
|
|
if e['interface'] == 'public'][0]['url']
|
|
return token_id, service_endpoint
|