Support Keystone V3 authentication

We now defer the majority of our auth to keystoneclient, rather
than continuing to maintain our custom code.

Change-Id: Ia8409940d3941bc82a8b54ec60e82efa6d043102
Closes-Bug: 1323435
This commit is contained in:
Kiall Mac Innes 2014-05-27 01:45:03 +01:00 committed by Endre Karlson
parent 9d4544fd51
commit 11425c0d01
4 changed files with 208 additions and 68 deletions

View File

@ -29,25 +29,29 @@ from designateclient.v1 import Client
class Command(CliffCommand):
def run(self, parsed_args):
client_args = {
kwargs = {
'endpoint': self.app.options.os_endpoint,
'auth_url': self.app.options.os_auth_url,
'username': self.app.options.os_username,
'user_id': self.app.options.os_user_id,
'user_domain_id': self.app.options.os_user_domain_id,
'user_domain_name': self.app.options.os_user_domain_name,
'password': self.app.options.os_password,
'tenant_id': self.app.options.os_tenant_id,
'tenant_name': self.app.options.os_tenant_name,
'tenant_id': self.app.options.os_tenant_id,
'domain_name': self.app.options.os_domain_name,
'domain_id': self.app.options.os_domain_id,
'project_name': self.app.options.os_project_name,
'project_id': self.app.options.os_project_id,
'project_domain_name': self.app.options.os_project_domain_name,
'project_domain_id': self.app.options.os_project_domain_id,
'auth_url': self.app.options.os_auth_url,
'token': self.app.options.os_token,
'endpoint_type': self.app.options.os_endpoint_type,
'service_type': self.app.options.os_service_type,
'region_name': self.app.options.os_region_name,
'sudo_tenant_id': self.app.options.sudo_tenant_id,
'insecure': self.app.options.insecure
'insecure': self.app.options.insecure,
}
if client_args['endpoint'] is None and client_args['auth_url'] is None:
raise ValueError('Either the --os-endpoint or --os-auth-url '
'argument must be supplied')
self.client = Client(**client_args)
self.client = Client(**kwargs)
try:
return super(Command, self).run(parsed_args)

View File

@ -23,6 +23,20 @@ from cliff.commandmanager import CommandManager
from designateclient.version import version_info as version
def env(*vars, **kwargs):
"""Search for the first defined of possibly many env vars
Returns the first environment variable defined in vars, or
returns the default defined in kwargs.
"""
for v in vars:
value = os.environ.get(v)
if value:
return value
return kwargs.get('default', '')
class DesignateShell(App):
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(message)s'
DEFAULT_VERBOSE_LEVEL = 0
@ -40,47 +54,103 @@ class DesignateShell(App):
parser = super(DesignateShell, self).build_option_parser(
description, version)
parser.add_argument('--os-endpoint',
default=os.environ.get('OS_DNS_ENDPOINT'),
help="Defaults to env[OS_DNS_ENDPOINT]")
parser.add_argument('--os-auth-url',
default=os.environ.get('OS_AUTH_URL'),
help="Defaults to env[OS_AUTH_URL]")
parser.add_argument('--os-username',
default=os.environ.get('OS_USERNAME'),
help="Defaults to env[OS_USERNAME]")
default=env('OS_USERNAME'),
help='Name used for authentication with the '
'OpenStack Identity service. '
'Defaults to env[OS_USERNAME].')
parser.add_argument('--os-user-id',
default=env('OS_USER_ID'),
help='User ID used for authentication with the '
'OpenStack Identity service. '
'Defaults to env[OS_USER_ID].')
parser.add_argument('--os-user-domain-id',
default=env('OS_USER_DOMAIN_ID'),
help='Defaults to env[OS_USER_DOMAIN_ID].')
parser.add_argument('--os-user-domain-name',
default=env('OS_USER_DOMAIN_NAME'),
help='Defaults to env[OS_USER_DOMAIN_NAME].')
parser.add_argument('--os-password',
default=os.environ.get('OS_PASSWORD'),
help="Defaults to env[OS_PASSWORD]")
parser.add_argument('--os-tenant-id',
default=os.environ.get('OS_TENANT_ID'),
help="Defaults to env[OS_TENANT_ID]")
default=env('OS_PASSWORD'),
help='Password used for authentication with the '
'OpenStack Identity service. '
'Defaults to env[OS_PASSWORD].')
parser.add_argument('--os-tenant-name',
default=os.environ.get('OS_TENANT_NAME'),
help="Defaults to env[OS_TENANT_NAME]")
default=env('OS_TENANT_NAME'),
help='Tenant to request authorization on. '
'Defaults to env[OS_TENANT_NAME].')
parser.add_argument('--os-token',
default=os.environ.get('OS_SERVICE_TOKEN'),
help="Defaults to env[OS_SERVICE_TOKEN]")
parser.add_argument('--os-tenant-id',
default=env('OS_TENANT_ID'),
help='Tenant to request authorization on. '
'Defaults to env[OS_TENANT_ID].')
parser.add_argument('--os-service-type',
default=os.environ.get('OS_DNS_SERVICE_TYPE',
'dns'),
help=("Defaults to env[OS_DNS_SERVICE_TYPE], or "
"'dns'"))
parser.add_argument('--os-project-name',
default=env('OS_PROJECT_NAME'),
help='Project to request authorization on. '
'Defaults to env[OS_PROJECT_NAME].')
parser.add_argument('--os-domain-name',
default=env('OS_DOMAIN_NAME'),
help='Project to request authorization on. '
'Defaults to env[OS_DOMAIN_NAME].')
parser.add_argument('--os-domain-id',
default=env('OS_DOMAIN_ID'),
help='Defaults to env[OS_DOMAIN_ID].')
parser.add_argument('--os-project-id',
default=env('OS_PROJECT_ID'),
help='Project to request authorization on. '
'Defaults to env[OS_PROJECT_ID].')
parser.add_argument('--os-project-domain-id',
default=env('OS_PROJECT_DOMAIN_ID'),
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
parser.add_argument('--os-project-domain-name',
default=env('OS_PROJECT_DOMAIN_NAME'),
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
parser.add_argument('--os-auth-url',
default=env('OS_AUTH_URL'),
help='Specify the Identity endpoint to use for '
'authentication. '
'Defaults to env[OS_AUTH_URL].')
parser.add_argument('--os-region-name',
default=os.environ.get('OS_REGION_NAME'),
help="Defaults to env[OS_REGION_NAME]")
default=env('OS_REGION_NAME'),
help='Specify the region to use. '
'Defaults to env[OS_REGION_NAME].')
parser.add_argument('--sudo-tenant-id',
default=os.environ.get('DESIGNATE_SUDO_TENANT_ID'),
help="Defaults to env[DESIGNATE_SUDO_TENANT_ID]")
parser.add_argument('--os-token',
default=env('OS_SERVICE_TOKEN'),
help='Specify an existing token to use instead of '
'retrieving one via authentication (e.g. '
'with username & password). '
'Defaults to env[OS_SERVICE_TOKEN].')
parser.add_argument('--os-endpoint',
default=env('OS_DNS_ENDPOINT',
'OS_SERVICE_ENDPOINT'),
help='Specify an endpoint to use instead of '
'retrieving one from the service catalog '
'(via authentication). '
'Defaults to env[OS_DNS_ENDPOINT].')
parser.add_argument('--os-endpoint-type',
default=env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE].')
parser.add_argument('--os-service-type',
default=env('OS_DNS_SERVICE_TYPE', default='dns'),
help=("Defaults to env[OS_DNS_SERVICE_TYPE], or "
"'dns'"))
parser.add_argument('--insecure', action='store_true',
help="Explicitly allow 'insecure' SSL requests")

View File

@ -17,7 +17,13 @@
import json
import os
from keystoneclient import client as ksclient
from keystoneclient.exceptions import DiscoveryFailure
from keystoneclient.v2_0 import client as v2_ksclient
from keystoneclient.v3 import client as v3_ksclient
import pkg_resources
import six.moves.urllib.parse as urlparse
from designateclient import exceptions
@ -91,3 +97,45 @@ def get_columns(data):
map(lambda item: map(_seen, item.keys()), data)
return list(columns)
def get_ksclient(username=None, user_id=None, user_domain_id=None,
user_domain_name=None, password=None, tenant_id=None,
tenant_name=None, domain_id=None, domain_name=None,
project_id=None, project_name=None,
project_domain_id=None, project_domain_name=None,
auth_url=None, token=None, insecure=None):
kwargs = {
'username': username,
'user_domain_id': user_domain_id,
'user_domain_name': user_domain_name,
'password': password,
'tenant_id': tenant_id,
'tenant_name': tenant_name,
'domain_id': domain_id,
'domain_name': domain_name,
'project_id': project_id,
'project_name': project_name,
'project_domain_id': project_domain_id,
'project_domain_name': project_domain_name,
'auth_url': auth_url,
'token': token,
'insecure': insecure
}
try:
return ksclient.Client(**kwargs)
except DiscoveryFailure:
# Discovery response mismatch. Raise the error
raise
except Exception:
# Some public clouds throw some other exception or doesn't support
# discovery. In that case try to determine version from auth_url
# 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'):
return v3_ksclient.Client(**kwargs)
elif path.startswith('/v2'):
return v2_ksclient.Client(**kwargs)

View File

@ -16,43 +16,50 @@
import requests
from stevedore import extension
from designateclient.auth import KeystoneAuth
from designateclient import exceptions
from designateclient import utils
class Client(object):
""" Client for the Designate v1 API """
def __init__(self, endpoint=None, auth_url=None, username=None,
password=None, tenant_id=None, tenant_name=None, token=None,
region_name=None, service_type='dns',
endpoint_type='publicURL', sudo_tenant_id=None,
def __init__(self, endpoint=None, username=None, user_id=None,
user_domain_id=None, user_domain_name=None, password=None,
tenant_name=None, tenant_id=None, domain_name=None,
domain_id=None, project_name=None,
project_id=None, project_domain_name=None,
project_domain_id=None, auth_url=None, token=None,
endpoint_type=None, region_name=None, service_type=None,
insecure=False):
"""
:param endpoint: Endpoint URL
:param auth_url: Keystone auth_url
:param username: The username to auth with
:param password: The password to auth with
:param tenant_id: The tenant ID
:param tenant_name: The tenant name
:param token: A token instead of username / password
:param region_name: The region name
:param endpoint_type: The endpoint type (publicURL for example)
:param insecure: Allow "insecure" HTTPS requests
"""
if auth_url:
auth = KeystoneAuth(auth_url, username, password, tenant_id,
tenant_name, token, service_type,
endpoint_type, region_name, sudo_tenant_id)
if endpoint:
self.endpoint = endpoint.rstrip('/')
else:
self.endpoint = auth.get_url()
elif endpoint:
auth = None
self.endpoint = endpoint.rstrip('/')
else:
raise ValueError('Either an endpoint or auth_url must be supplied')
if not endpoint or not token:
ksclient = utils.get_ksclient(
username=username, user_id=user_id,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name, password=password,
tenant_id=tenant_id, tenant_name=tenant_name,
project_id=project_id, project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name,
auth_url=auth_url,
token=token,
insecure=insecure)
ksclient.authenticate()
token = token or ksclient.auth_token
filters = {
'region_name': region_name,
'service_type': service_type,
'endpoint_type': endpoint_type,
}
endpoint = endpoint or self._get_endpoint(ksclient, **filters)
self.endpoint = endpoint.rstrip('/')
# NOTE(kiall): As we're in the Version 1 client, we ensure we're
# pointing at the version 1 API.
@ -67,7 +74,6 @@ class Client(object):
headers['X-Auth-Token'] = token
self.requests = requests.Session()
self.requests.auth = auth
self.requests.headers.update(headers)
def _load_controller(ext):
@ -113,6 +119,18 @@ class Client(object):
else:
return response
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 'dns',
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 'dns',
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
def get(self, path, **kw):
return self.wrap_api_call(self.requests.get, path, **kw)