Remove RAX-specific auth in troveclient

Author: Swapnil Kulkarni <swapnilkulkarni2608@gmail.com>
Co-Authored-By: Nikhil Manchanda <SlickNik@gmail.com>
Co-Authored-By: Craig Vyvial <cp16net@gmail.com>

Change-Id: I250777890a1f5240c5f14290cf02eb5a7b34b434
Closes-Bug: #966329
This commit is contained in:
Swapnil Kulkarni 2014-01-25 08:25:25 +05:30 committed by Craig Vyvial
parent 4d8e1567ec
commit cd58da5213
4 changed files with 173 additions and 43 deletions

107
troveclient/auth_plugin.py Normal file

@ -0,0 +1,107 @@
# Copyright 2014 Rackspace
# Copyright 2013 OpenStack Foundation
# Copyright 2013 Spanish National Research Council.
# All Rights Reserved.
#
# 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 logging
import pkg_resources
import six
from troveclient import exceptions
from troveclient.openstack.common.gettextutils import _ # noqa
logger = logging.getLogger(__name__)
_discovered_plugins = {}
def discover_auth_systems():
"""Discover the available auth-systems.
This won't take into account the old style auth-systems.
"""
ep_name = 'openstack.client.auth_plugin'
for ep in pkg_resources.iter_entry_points(ep_name):
try:
auth_plugin = ep.load()
except (ImportError, pkg_resources.UnknownExtra, AttributeError) as e:
logger.debug(_("ERROR: Cannot load auth plugin %s") % ep.name)
logger.debug(e, exc_info=1)
else:
_discovered_plugins[ep.name] = auth_plugin
def load_auth_system_opts(parser):
"""Load options needed by the available auth-systems into a parser.
This function will try to populate the parser with options from the
available plugins.
"""
for name, auth_plugin in six.iteritems(_discovered_plugins):
add_opts_fn = getattr(auth_plugin, "add_opts", None)
if add_opts_fn:
group = parser.add_argument_group("Auth-system '%s' options" %
name)
add_opts_fn(group)
def load_plugin(auth_system):
if auth_system in _discovered_plugins:
return _discovered_plugins[auth_system]()
raise exceptions.AuthSystemNotFound(auth_system)
class BaseAuthPlugin(object):
"""Base class for authentication plugins.
An authentication plugin needs to override at least the authenticate
method to be a valid plugin.
"""
def __init__(self):
self.opts = {}
def get_auth_url(self):
"""Return the auth url for the plugin (if any)."""
return None
@staticmethod
def add_opts(parser):
"""Populate and return the parser with the options for this plugin.
If the plugin does not need any options, it should return the same
parser untouched.
"""
return parser
def parse_opts(self, args):
"""Parse the actual auth-system options if any.
This method is expected to populate the attribute self.opts with a
dict containing the options and values needed to make authentication.
If the dict is empty, the client should assume that it needs the same
options as the 'keystone' auth system (i.e. os_username and
os_password).
Returns the self.opts dict.
"""
return self.opts
def authenticate(self, cls, auth_url):
"""Authenticate using plugin defined method."""
raise exceptions.AuthSystemNotFound(self.auth_system)

@ -22,7 +22,6 @@ OpenStack Client interface. Handles the REST calls and responses.
from __future__ import print_function
import logging
import os
import requests
from keystoneclient import adapter
@ -89,7 +88,16 @@ class HTTPClient(TroveClientMixin):
self.password = password
self.projectid = projectid
self.tenant_id = tenant_id
self.auth_url = auth_url.rstrip('/')
if auth_system and auth_system != 'keystone' and not auth_plugin:
raise exceptions.AuthSystemNotFound(auth_system)
if not auth_url and auth_system and auth_system != 'keystone':
auth_url = auth_plugin.get_auth_url()
if not auth_url:
raise exceptions.EndpointNotFound()
self.auth_url = auth_url.rstrip('/') if auth_url else auth_url
self.version = 'v1'
self.region_name = region_name
self.endpoint_type = endpoint_type
@ -105,6 +113,8 @@ class HTTPClient(TroveClientMixin):
self.proxy_tenant_id = proxy_tenant_id
self.timeout = timeout
self.bypass_url = bypass_url
self.auth_system = auth_system
self.auth_plugin = auth_plugin
if insecure:
self.verify_cert = False
@ -326,10 +336,10 @@ class HTTPClient(TroveClientMixin):
auth_url = self.auth_url
if self.version == "v2.0":
while auth_url:
if "TROVE_RAX_AUTH" in os.environ:
auth_url = self._rax_auth(auth_url)
else:
if not self.auth_system or self.auth_system == 'keystone':
auth_url = self._v2_auth(auth_url)
else:
auth_url = self._plugin_auth(auth_url)
# Are we acting on behalf of another user via an
# existing token? If so, our actual endpoints may
@ -357,6 +367,9 @@ class HTTPClient(TroveClientMixin):
if self.bypass_url is not None and self.bypass_url != '':
self.management_url = self.bypass_url
def _plugin_auth(self, auth_url):
return self.auth_plugin.authenticate(self, auth_url)
def _v1_auth(self, url):
if self.proxy_token:
raise exceptions.NoTokenLookupException()
@ -393,16 +406,6 @@ class HTTPClient(TroveClientMixin):
self._authenticate(url, body)
def _rax_auth(self, url):
"""Authenticate against the Rackspace auth service."""
body = {"auth": {
"RAX-KSKEY:apiKeyCredentials": {
"username": self.user,
"apiKey": self.password,
"tenantName": self.projectid}}}
self._authenticate(url, body)
def _authenticate(self, url, body):
"""Authenticate and extract the service catalog."""
token_url = url + "/tokens"

@ -21,6 +21,7 @@ Command-line interface to the OpenStack Trove API.
from __future__ import print_function
import argparse
import getpass
import glob
import imp
import itertools
@ -38,9 +39,9 @@ from keystoneclient.auth.identity import v3 as identity
from keystoneclient import session as ks_session
import troveclient
import troveclient.extension
import troveclient.auth_plugin
from troveclient import client
import troveclient.extension
from troveclient.openstack.common.apiclient import exceptions as exc
from troveclient.openstack.common import gettextutils as gtu
from troveclient.openstack.common.gettextutils import _ # noqa
@ -107,7 +108,10 @@ class OpenStackTroveShell(object):
parser.add_argument('--os-auth-system',
metavar='<auth-system>',
default=utils.env('OS_AUTH_SYSTEM'))
default=utils.env('OS_AUTH_SYSTEM'),
help='Defaults to env[OS_AUTH_SYSTEM].')
parser.add_argument('--os_auth_system',
help=argparse.SUPPRESS)
parser.add_argument('--service-type',
metavar='<service-type>',
@ -175,6 +179,9 @@ class OpenStackTroveShell(object):
self._append_global_identity_args(parser)
# The auth-system-plugins might require some extra options
troveclient.auth_plugin.load_auth_system_opts(parser)
return parser
def _append_global_identity_args(self, parser):
@ -332,6 +339,9 @@ class OpenStackTroveShell(object):
self.setup_debugging(options.debug)
self.options = options
# Discover available auth plugins
troveclient.auth_plugin.discover_auth_systems()
# build available subcommands based on version
self.extensions = self._discover_extensions(
options.os_database_api_version)
@ -356,17 +366,20 @@ class OpenStackTroveShell(object):
self.do_bash_completion(args)
return 0
(os_username, os_password, os_tenant_name, os_auth_url,
os_region_name, os_tenant_id, endpoint_type, insecure,
service_type, service_name, database_service_name,
cacert, bypass_url, os_auth_system) = (
args.os_username, args.os_password,
args.os_tenant_name, args.os_auth_url,
args.os_region_name, args.os_tenant_id,
args.endpoint_type, args.insecure,
args.service_type, args.service_name,
args.database_service_name,
args.os_cacert, args.bypass_url, args.os_auth_system)
os_username = args.os_username
os_password = args.os_password
os_tenant_name = args.os_tenant_name
os_auth_url = args.os_auth_url
os_region_name = args.os_region_name
os_tenant_id = args.os_tenant_id
os_auth_system = args.os_auth_system
endpoint_type = args.endpoint_type
insecure = args.insecure
service_type = args.service_type
service_name = args.service_name
database_service_name = args.database_service_name
cacert = args.os_cacert
bypass_url = args.bypass_url
if os_auth_system and os_auth_system != "keystone":
auth_plugin = troveclient.auth_plugin.load_plugin(os_auth_system)
@ -384,20 +397,22 @@ class OpenStackTroveShell(object):
# for os_username or os_password but for compatibility it is not.
if not utils.isunauthenticated(args.func):
if not os_username:
raise exc.CommandError(
"You must provide a username "
"via either --os-username or env[OS_USERNAME]")
if auth_plugin:
auth_plugin.parse_opts(args)
if not auth_plugin or not auth_plugin.opts:
if not os_username:
raise exc.CommandError(
"You must provide a username "
"via either --os-username or env[OS_USERNAME]")
if not os_password:
raise exc.CommandError("You must provide a password "
"via either --os-password or via "
"env[OS_PASSWORD]")
os_password = getpass.getpass()
if not os_auth_url:
raise exc.CommandError(
"You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL]")
if os_auth_system and os_auth_system != 'keystone':
os_auth_url = auth_plugin.get_auth_url()
# V3 stuff
project_info_provided = (self.options.os_tenant_name or
@ -422,9 +437,12 @@ class OpenStackTroveShell(object):
"(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]")
raise exc.CommandError("You must provide an auth url "
"via either --os-auth-url or "
"env[OS_AUTH_URL] or specify an "
"auth_system which defines a default "
"url with --os-auth-system or "
"env[OS_AUTH_SYSTEM]")
use_session = True
if auth_plugin or bypass_url:

@ -150,7 +150,9 @@ class ShellTest(testtools.TestCase):
def test_no_auth_url(self):
required = ('You must provide an auth url'
' via either --os-auth-url or env[OS_AUTH_URL]',)
' via either --os-auth-url or env[OS_AUTH_URL] '
'or specify an auth_system which defines a default '
'url with --os-auth-system or env[OS_AUTH_SYSTEM]',)
self.make_env(exclude='OS_AUTH_URL')
try:
self.shell('list')