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:
parent
4d8e1567ec
commit
cd58da5213
troveclient
107
troveclient/auth_plugin.py
Normal file
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
|
from __future__ import print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from keystoneclient import adapter
|
from keystoneclient import adapter
|
||||||
@ -89,7 +88,16 @@ class HTTPClient(TroveClientMixin):
|
|||||||
self.password = password
|
self.password = password
|
||||||
self.projectid = projectid
|
self.projectid = projectid
|
||||||
self.tenant_id = tenant_id
|
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.version = 'v1'
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.endpoint_type = endpoint_type
|
self.endpoint_type = endpoint_type
|
||||||
@ -105,6 +113,8 @@ class HTTPClient(TroveClientMixin):
|
|||||||
self.proxy_tenant_id = proxy_tenant_id
|
self.proxy_tenant_id = proxy_tenant_id
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.bypass_url = bypass_url
|
self.bypass_url = bypass_url
|
||||||
|
self.auth_system = auth_system
|
||||||
|
self.auth_plugin = auth_plugin
|
||||||
|
|
||||||
if insecure:
|
if insecure:
|
||||||
self.verify_cert = False
|
self.verify_cert = False
|
||||||
@ -326,10 +336,10 @@ class HTTPClient(TroveClientMixin):
|
|||||||
auth_url = self.auth_url
|
auth_url = self.auth_url
|
||||||
if self.version == "v2.0":
|
if self.version == "v2.0":
|
||||||
while auth_url:
|
while auth_url:
|
||||||
if "TROVE_RAX_AUTH" in os.environ:
|
if not self.auth_system or self.auth_system == 'keystone':
|
||||||
auth_url = self._rax_auth(auth_url)
|
|
||||||
else:
|
|
||||||
auth_url = self._v2_auth(auth_url)
|
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
|
# Are we acting on behalf of another user via an
|
||||||
# existing token? If so, our actual endpoints may
|
# 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 != '':
|
if self.bypass_url is not None and self.bypass_url != '':
|
||||||
self.management_url = 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):
|
def _v1_auth(self, url):
|
||||||
if self.proxy_token:
|
if self.proxy_token:
|
||||||
raise exceptions.NoTokenLookupException()
|
raise exceptions.NoTokenLookupException()
|
||||||
@ -393,16 +406,6 @@ class HTTPClient(TroveClientMixin):
|
|||||||
|
|
||||||
self._authenticate(url, body)
|
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):
|
def _authenticate(self, url, body):
|
||||||
"""Authenticate and extract the service catalog."""
|
"""Authenticate and extract the service catalog."""
|
||||||
token_url = url + "/tokens"
|
token_url = url + "/tokens"
|
||||||
|
@ -21,6 +21,7 @@ Command-line interface to the OpenStack Trove API.
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import getpass
|
||||||
import glob
|
import glob
|
||||||
import imp
|
import imp
|
||||||
import itertools
|
import itertools
|
||||||
@ -38,9 +39,9 @@ from keystoneclient.auth.identity import v3 as identity
|
|||||||
from keystoneclient import session as ks_session
|
from keystoneclient import session as ks_session
|
||||||
|
|
||||||
import troveclient
|
import troveclient
|
||||||
import troveclient.extension
|
import troveclient.auth_plugin
|
||||||
|
|
||||||
from troveclient import client
|
from troveclient import client
|
||||||
|
import troveclient.extension
|
||||||
from troveclient.openstack.common.apiclient import exceptions as exc
|
from troveclient.openstack.common.apiclient import exceptions as exc
|
||||||
from troveclient.openstack.common import gettextutils as gtu
|
from troveclient.openstack.common import gettextutils as gtu
|
||||||
from troveclient.openstack.common.gettextutils import _ # noqa
|
from troveclient.openstack.common.gettextutils import _ # noqa
|
||||||
@ -107,7 +108,10 @@ class OpenStackTroveShell(object):
|
|||||||
|
|
||||||
parser.add_argument('--os-auth-system',
|
parser.add_argument('--os-auth-system',
|
||||||
metavar='<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',
|
parser.add_argument('--service-type',
|
||||||
metavar='<service-type>',
|
metavar='<service-type>',
|
||||||
@ -175,6 +179,9 @@ class OpenStackTroveShell(object):
|
|||||||
|
|
||||||
self._append_global_identity_args(parser)
|
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
|
return parser
|
||||||
|
|
||||||
def _append_global_identity_args(self, parser):
|
def _append_global_identity_args(self, parser):
|
||||||
@ -332,6 +339,9 @@ class OpenStackTroveShell(object):
|
|||||||
self.setup_debugging(options.debug)
|
self.setup_debugging(options.debug)
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
|
# Discover available auth plugins
|
||||||
|
troveclient.auth_plugin.discover_auth_systems()
|
||||||
|
|
||||||
# build available subcommands based on version
|
# build available subcommands based on version
|
||||||
self.extensions = self._discover_extensions(
|
self.extensions = self._discover_extensions(
|
||||||
options.os_database_api_version)
|
options.os_database_api_version)
|
||||||
@ -356,17 +366,20 @@ class OpenStackTroveShell(object):
|
|||||||
self.do_bash_completion(args)
|
self.do_bash_completion(args)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
(os_username, os_password, os_tenant_name, os_auth_url,
|
os_username = args.os_username
|
||||||
os_region_name, os_tenant_id, endpoint_type, insecure,
|
os_password = args.os_password
|
||||||
service_type, service_name, database_service_name,
|
os_tenant_name = args.os_tenant_name
|
||||||
cacert, bypass_url, os_auth_system) = (
|
os_auth_url = args.os_auth_url
|
||||||
args.os_username, args.os_password,
|
os_region_name = args.os_region_name
|
||||||
args.os_tenant_name, args.os_auth_url,
|
os_tenant_id = args.os_tenant_id
|
||||||
args.os_region_name, args.os_tenant_id,
|
os_auth_system = args.os_auth_system
|
||||||
args.endpoint_type, args.insecure,
|
endpoint_type = args.endpoint_type
|
||||||
args.service_type, args.service_name,
|
insecure = args.insecure
|
||||||
args.database_service_name,
|
service_type = args.service_type
|
||||||
args.os_cacert, args.bypass_url, args.os_auth_system)
|
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":
|
if os_auth_system and os_auth_system != "keystone":
|
||||||
auth_plugin = troveclient.auth_plugin.load_plugin(os_auth_system)
|
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.
|
# for os_username or os_password but for compatibility it is not.
|
||||||
|
|
||||||
if not utils.isunauthenticated(args.func):
|
if not utils.isunauthenticated(args.func):
|
||||||
if not os_username:
|
|
||||||
raise exc.CommandError(
|
if auth_plugin:
|
||||||
"You must provide a username "
|
auth_plugin.parse_opts(args)
|
||||||
"via either --os-username or env[OS_USERNAME]")
|
|
||||||
|
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:
|
if not os_password:
|
||||||
raise exc.CommandError("You must provide a password "
|
os_password = getpass.getpass()
|
||||||
"via either --os-password or via "
|
|
||||||
"env[OS_PASSWORD]")
|
|
||||||
|
|
||||||
if not os_auth_url:
|
if not os_auth_url:
|
||||||
raise exc.CommandError(
|
if os_auth_system and os_auth_system != 'keystone':
|
||||||
"You must provide an auth url "
|
os_auth_url = auth_plugin.get_auth_url()
|
||||||
"via either --os-auth-url or env[OS_AUTH_URL]")
|
|
||||||
|
|
||||||
# V3 stuff
|
# V3 stuff
|
||||||
project_info_provided = (self.options.os_tenant_name or
|
project_info_provided = (self.options.os_tenant_name or
|
||||||
@ -422,9 +437,12 @@ class OpenStackTroveShell(object):
|
|||||||
"(env[OS_PROJECT_DOMAIN_NAME])"))
|
"(env[OS_PROJECT_DOMAIN_NAME])"))
|
||||||
|
|
||||||
if not os_auth_url:
|
if not os_auth_url:
|
||||||
raise exc.CommandError(
|
raise exc.CommandError("You must provide an auth url "
|
||||||
"You must provide an auth url "
|
"via either --os-auth-url or "
|
||||||
"via either --os-auth-url or env[OS_AUTH_URL]")
|
"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
|
use_session = True
|
||||||
if auth_plugin or bypass_url:
|
if auth_plugin or bypass_url:
|
||||||
|
@ -150,7 +150,9 @@ class ShellTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_no_auth_url(self):
|
def test_no_auth_url(self):
|
||||||
required = ('You must provide an auth url'
|
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')
|
self.make_env(exclude='OS_AUTH_URL')
|
||||||
try:
|
try:
|
||||||
self.shell('list')
|
self.shell('list')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user