Split plugin loading

One of the issues raised with keystoneclient plugins is the way that
loading of a plugin is mixed in with the definition of a plugin. Amongst
other things this really meant that there was only one way to utilize
each plugin class, to do another plugin that utilized an existing one
you would have to define a new class and proxy to it.

In this patch we split the concerns. There is a whole new section called
loading that is solely responsible for ways to load the auth plugin from
CLI or from argparse and other future methods.

This new section will likely be split into its own repository.

Change-Id: I8387b86fc0e3a8403f9806440196e8723eaf1ee4
This commit is contained in:
Jamie Lennox 2015-08-03 09:32:56 +10:00
parent a08ca8f494
commit efcadd6937
36 changed files with 656 additions and 544 deletions

View File

@ -1,38 +0,0 @@
# 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 keystoneauth1.base import * # noqa
from keystoneauth1.cli import * # noqa
from keystoneauth1.conf import * # noqa
__all__ = [
# auth.base
'AUTH_INTERFACE',
'BaseAuthPlugin',
'get_available_plugin_names',
'get_available_plugin_classes',
'get_plugin_class',
'IDENTITY_AUTH_HEADER_NAME',
'PLUGIN_NAMESPACE',
# auth.cli
'load_from_argparse_arguments',
'register_argparse_arguments',
# auth.conf
'get_common_conf_options',
'get_plugin_options',
'load_from_conf_options',
'register_conf_options',
]

View File

@ -13,25 +13,18 @@
import abc
import logging
from oslo_config import cfg
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import base
from keystoneauth1 import discover
from keystoneauth1 import exceptions
from keystoneauth1 import plugin
LOG = logging.getLogger(__name__)
def get_options():
return [
cfg.StrOpt('auth-url', help='Authentication URL'),
]
@six.add_metaclass(abc.ABCMeta)
class BaseIdentityPlugin(base.BaseAuthPlugin):
class BaseIdentityPlugin(plugin.BaseAuthPlugin):
# we count a token as valid (not needing refreshing) if it is valid for at
# least this many seconds before the token expiry time
@ -201,7 +194,7 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
# are asking for the auth endpoint it means that there is no catalog to
# query however we still need to support asking for a specific version
# of the auth_url for generic plugins.
if interface is base.AUTH_INTERFACE:
if interface is plugin.AUTH_INTERFACE:
url = self.auth_url
service_type = service_type or 'identity'
@ -318,9 +311,3 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
session_endpoint_cache[url] = disc
return disc
@classmethod
def get_options(cls):
options = super(BaseIdentityPlugin, cls).get_options()
options.extend(get_options())
return options

View File

@ -13,7 +13,6 @@
import abc
import logging
from oslo_config import cfg
import six
import six.moves.urllib.parse as urlparse
@ -25,22 +24,6 @@ from keystoneauth1.identity import base
LOG = logging.getLogger(__name__)
def get_options():
return [
cfg.StrOpt('domain-id', help='Domain ID to scope to'),
cfg.StrOpt('domain-name', help='Domain name to scope to'),
cfg.StrOpt('tenant-id', help='Tenant ID to scope to'),
cfg.StrOpt('tenant-name', help='Tenant name to scope to'),
cfg.StrOpt('project-id', help='Project ID to scope to'),
cfg.StrOpt('project-name', help='Project name to scope to'),
cfg.StrOpt('project-domain-id',
help='Domain ID containing project'),
cfg.StrOpt('project-domain-name',
help='Domain name containing project'),
cfg.StrOpt('trust-id', help='Trust ID'),
]
@six.add_metaclass(abc.ABCMeta)
class BaseGenericPlugin(base.BaseIdentityPlugin):
"""An identity plugin that is not version dependant.
@ -173,9 +156,3 @@ class BaseGenericPlugin(base.BaseIdentityPlugin):
self._plugin = self._do_create_plugin(session)
return self._plugin.get_auth_ref(session, **kwargs)
@classmethod
def get_options(cls):
options = super(BaseGenericPlugin, cls).get_options()
options.extend(get_options())
return options

View File

@ -12,8 +12,6 @@
import logging
from oslo_config import cfg
from keystoneauth1 import _utils as utils
from keystoneauth1 import discover
from keystoneauth1.identity.generic import base
@ -23,17 +21,6 @@ from keystoneauth1.identity import v3
LOG = logging.getLogger(__name__)
def get_options():
return [
cfg.StrOpt('user-id', help='User id'),
cfg.StrOpt('user-name', dest='username', help='Username',
deprecated_name='username'),
cfg.StrOpt('user-domain-id', help="User's domain id"),
cfg.StrOpt('user-domain-name', help="User's domain name"),
cfg.StrOpt('password', help="User's password"),
]
class Password(base.BaseGenericPlugin):
"""A common user/password authentication plugin.
@ -76,9 +63,3 @@ class Password(base.BaseGenericPlugin):
user_domain_name=self._user_domain_name,
password=self._password,
**self._v3_params)
@classmethod
def get_options(cls):
options = super(Password, cls).get_options()
options.extend(get_options())
return options

View File

@ -12,8 +12,6 @@
import logging
from oslo_config import cfg
from keystoneauth1 import discover
from keystoneauth1.identity.generic import base
from keystoneauth1.identity import v2
@ -22,12 +20,6 @@ from keystoneauth1.identity import v3
LOG = logging.getLogger(__name__)
def get_options():
return [
cfg.StrOpt('token', help='Token to authenticate with'),
]
class Token(base.BaseGenericPlugin):
"""Generic token auth plugin.
@ -44,9 +36,3 @@ class Token(base.BaseGenericPlugin):
elif discover.version_match((3,), version):
return v3.Token(url, self._token, **self._v3_params)
@classmethod
def get_options(cls):
options = super(Token, cls).get_options()
options.extend(get_options())
return options

View File

@ -13,7 +13,6 @@
import abc
import logging
from oslo_config import cfg
import six
from keystoneauth1 import _utils as utils
@ -36,18 +35,6 @@ class Auth(base.BaseIdentityPlugin):
is going to expire. (optional) default True
"""
@classmethod
def get_options(cls):
options = super(Auth, cls).get_options()
options.extend([
cfg.StrOpt('tenant-id', help='Tenant ID'),
cfg.StrOpt('tenant-name', help='Tenant Name'),
cfg.StrOpt('trust-id', help='Trust ID'),
])
return options
@utils.positional()
def __init__(self, auth_url,
trust_id=None,
@ -147,21 +134,6 @@ class Password(Auth):
return {'passwordCredentials': auth}
@classmethod
def get_options(cls):
options = super(Password, cls).get_options()
options.extend([
cfg.StrOpt('user-name',
dest='username',
deprecated_name='username',
help='Username to login with'),
cfg.StrOpt('user-id', help='User ID to login with'),
cfg.StrOpt('password', secret=True, help='Password to use'),
])
return options
class Token(Auth):
"""A plugin for authenticating with an existing token.
@ -183,13 +155,3 @@ class Token(Auth):
if headers is not None:
headers['X-Auth-Token'] = self.token
return {'token': {'id': self.token}}
@classmethod
def get_options(cls):
options = super(Token, cls).get_options()
options.extend([
cfg.StrOpt('token', secret=True, help='Token'),
])
return options

View File

@ -13,7 +13,6 @@
import abc
import logging
from oslo_config import cfg
import six
from keystoneauth1 import _utils as utils
@ -76,24 +75,6 @@ class BaseAuth(base.BaseIdentityPlugin):
def get_auth_ref(self, session, **kwargs):
return None
@classmethod
def get_options(cls):
options = super(BaseAuth, cls).get_options()
options.extend([
cfg.StrOpt('domain-id', help='Domain ID to scope to'),
cfg.StrOpt('domain-name', help='Domain name to scope to'),
cfg.StrOpt('project-id', help='Project ID to scope to'),
cfg.StrOpt('project-name', help='Project name to scope to'),
cfg.StrOpt('project-domain-id',
help='Domain ID containing project'),
cfg.StrOpt('project-domain-name',
help='Domain name containing project'),
cfg.StrOpt('trust-id', help='Trust ID'),
])
return options
class Auth(BaseAuth):
"""Identity V3 Authentication Plugin.

View File

@ -12,7 +12,6 @@
import abc
from oslo_config import cfg
import six
from keystoneauth1.identity.v3 import base
@ -45,19 +44,6 @@ class FederationBaseAuth(base.BaseAuth):
self.identity_provider = identity_provider
self.protocol = protocol
@classmethod
def get_options(cls):
options = super(FederationBaseAuth, cls).get_options()
options.extend([
cfg.StrOpt('identity-provider',
help="Identity Provider's name"),
cfg.StrOpt('protocol',
help='Protocol for federated plugin'),
])
return options
@property
def federated_token_url(self):
"""Full URL where authorization data is sent."""

View File

@ -10,13 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from keystoneauth1 import access
from keystoneauth1 import base as auth_base
from keystoneauth1 import exceptions
from keystoneauth1.identity.v3 import base
from keystoneauth1.identity.v3 import token
from keystoneauth1 import plugin
__all__ = ['Keystone2Keystone']
@ -83,16 +81,6 @@ class Keystone2Keystone(base.BaseAuth):
'project_domain_id': self.project_domain_id,
'project_domain_name': self.project_domain_name}
@classmethod
def get_options(cls):
options = super(Keystone2Keystone, cls).get_options()
options.extend([
cfg.StrOpt("service-provider", help="Service Provider's ID")
])
return options
def _ecp_assertion_request(self, session):
token_id = self._local_cloud_plugin.get_access(session).auth_token
body = {
@ -115,7 +103,7 @@ class Keystone2Keystone(base.BaseAuth):
def _get_ecp_assertion(self, session):
url = self._local_cloud_plugin.get_endpoint(
session, interface=auth_base.AUTH_INTERFACE)
session, interface=plugin.AUTH_INTERFACE)
body = self._ecp_assertion_request(session)
resp = session.post(url=url + self.REQUEST_ECP_URL, json=body,

View File

@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from keystoneauth1.identity.v3 import base
@ -71,18 +69,3 @@ class Password(base.AuthConstructor):
"""
_auth_method_class = PasswordMethod
@classmethod
def get_options(cls):
options = super(Password, cls).get_options()
options.extend([
cfg.StrOpt('user-id', help='User ID'),
cfg.StrOpt('user-name', dest='username', help='Username',
deprecated_name='username'),
cfg.StrOpt('user-domain-id', help="User's domain id"),
cfg.StrOpt('user-domain-name', help="User's domain name"),
cfg.StrOpt('password', secret=True, help="User's password"),
])
return options

View File

@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from keystoneauth1.identity.v3 import base
@ -51,15 +49,3 @@ class Token(base.AuthConstructor):
def __init__(self, auth_url, token, **kwargs):
super(Token, self).__init__(auth_url, token=token, **kwargs)
@classmethod
def get_options(cls):
options = super(Token, cls).get_options()
options.extend([
cfg.StrOpt('token',
secret=True,
help='Token to authenticate with'),
])
return options

View File

@ -0,0 +1,35 @@
# 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 keystoneauth1.loading.base import * # noqa
from keystoneauth1.loading.cli import * # noqa
from keystoneauth1.loading.conf import * # noqa
__all__ = [
# loading.base
'BaseLoader',
'get_available_plugin_names',
'get_available_plugin_loaders',
'get_plugin_loader',
'PLUGIN_NAMESPACE',
# loading.cli
'load_from_argparse_arguments',
'register_argparse_arguments',
# loading.conf
'get_common_conf_options',
'get_plugin_options',
'load_from_conf_options',
'register_conf_options',
]

View File

@ -0,0 +1,27 @@
# 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 oslo_config import cfg
from keystoneauth1 import loading
class BaseIdentityLoader(loading.BaseLoader):
def get_options(self):
options = super(BaseIdentityLoader, self).get_options()
options.extend([
cfg.StrOpt('auth-url', help='Authentication URL'),
])
return options

View File

@ -0,0 +1,75 @@
# 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 oslo_config import cfg
from keystoneauth1 import identity
from keystoneauth1.loading._plugins.identity import base
class GenericBaseLoader(base.BaseIdentityLoader):
def get_options(self):
options = super(GenericBaseLoader, self).get_options()
options.extend([
cfg.StrOpt('domain-id', help='Domain ID to scope to'),
cfg.StrOpt('domain-name', help='Domain name to scope to'),
cfg.StrOpt('tenant-id', help='Tenant ID to scope to'),
cfg.StrOpt('tenant-name', help='Tenant name to scope to'),
cfg.StrOpt('project-id', help='Project ID to scope to'),
cfg.StrOpt('project-name', help='Project name to scope to'),
cfg.StrOpt('project-domain-id',
help='Domain ID containing project'),
cfg.StrOpt('project-domain-name',
help='Domain name containing project'),
cfg.StrOpt('trust-id', help='Trust ID'),
])
return options
class Token(GenericBaseLoader):
@property
def plugin_class(self):
return identity.Token
def get_options(self):
options = super(Token, self).get_options()
options.extend([
cfg.StrOpt('token', help='Token to authenticate with'),
])
return options
class Password(GenericBaseLoader):
@property
def plugin_class(self):
return identity.Password
def get_options(cls):
options = super(Password, cls).get_options()
options.extend([
cfg.StrOpt('user-id', help='User id'),
cfg.StrOpt('user-name',
dest='username',
help='Username',
deprecated_name='username'),
cfg.StrOpt('user-domain-id', help="User's domain id"),
cfg.StrOpt('user-domain-name', help="User's domain name"),
cfg.StrOpt('password', help="User's password"),
])
return options

View File

@ -0,0 +1,67 @@
# 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 oslo_config import cfg
from keystoneauth1 import identity
from keystoneauth1.loading._plugins.identity import base
class BaseV2Loader(base.BaseIdentityLoader):
def get_options(self):
options = super(BaseV2Loader, self).get_options()
options.extend([
cfg.StrOpt('tenant-id', help='Tenant ID'),
cfg.StrOpt('tenant-name', help='Tenant Name'),
cfg.StrOpt('trust-id', help='Trust ID'),
])
return options
class V2Token(BaseV2Loader):
@property
def plugin_class(self):
return identity.V2Token
def get_options(self):
options = super(V2Token, self).get_options()
options.extend([
cfg.StrOpt('token', secret=True, help='Token'),
])
return options
class V2Password(BaseV2Loader):
@property
def plugin_class(self):
return identity.V2Password
def get_options(self):
options = super(V2Password, self).get_options()
options.extend([
cfg.StrOpt('user-name',
dest='username',
deprecated_name='username',
help='Username to login with'),
cfg.StrOpt('user-id', help='User ID to longin with'),
cfg.StrOpt('password', secret=True, help='Password to use'),
])
return options

View File

@ -0,0 +1,92 @@
# 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 oslo_config import cfg
from keystoneauth1 import identity
from keystoneauth1.loading._plugins.identity import base
class BaseV3Loader(base.BaseIdentityLoader):
def get_options(self):
options = super(BaseV3Loader, self).get_options()
options.extend([
cfg.StrOpt('domain-id', help='Domain ID to scope to'),
cfg.StrOpt('domain-name', help='Domain name to scope to'),
cfg.StrOpt('project-id', help='Project ID to scope to'),
cfg.StrOpt('project-name', help='Project name to scope to'),
cfg.StrOpt('project-domain-id',
help='Domain ID containing project'),
cfg.StrOpt('project-domain-name',
help='Domain name containing project'),
cfg.StrOpt('trust-id', help='Trust ID'),
])
return options
class Password(BaseV3Loader):
@property
def plugin_class(self):
return identity.V3Password
def get_options(self):
options = super(Password, self).get_options()
options.extend([
cfg.StrOpt('user-id', help='User ID'),
cfg.StrOpt('user-name',
dest='username',
help='Username',
deprecated_name='username'),
cfg.StrOpt('user-domain-id', help="User's domain id"),
cfg.StrOpt('user-domain-name', help="User's domain name"),
cfg.StrOpt('password', secret=True, help="User's password"),
])
return options
class Token(BaseV3Loader):
@property
def plugin_class(self):
return identity.Token
def get_options(self):
options = super(Token, self).get_options()
options.extend([
cfg.StrOpt('token',
secret=True,
help='Token to authenticate with'),
])
return options
class FederatedBase(BaseV3Loader):
def get_options(self):
options = super(FederatedBase, self).get_options()
options.extend([
cfg.StrOpt('identity-provider',
help="Identity Provider's name"),
cfg.StrOpt('protocol',
help='Protocol for federated plugin'),
])
return options

View File

@ -0,0 +1,36 @@
# 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 oslo_config import cfg
from keystoneauth1 import loading
from keystoneauth1 import token_endpoint
class TokenEndpoint(loading.BaseLoader):
@property
def plugin_class(self):
return token_endpoint.TokenEndpoint
def get_options(self):
options = super(TokenEndpoint, self).get_options()
options.extend([
cfg.StrOpt('endpoint',
help='The endpoint that will always be used'),
cfg.StrOpt('token',
secret=True,
help='The token that will always be used'),
])
return options

View File

@ -0,0 +1,202 @@
# 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 abc
import os
import six
import stevedore
from keystoneauth1 import exceptions
PLUGIN_NAMESPACE = 'keystoneauth1.plugin'
def get_available_plugin_names():
"""Get the names of all the plugins that are available on the system.
This is particularly useful for help and error text to prompt a user for
example what plugins they may specify.
:returns: A list of names.
:rtype: frozenset
"""
mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE)
return frozenset(mgr.names())
def get_available_plugin_loaders():
"""Retrieve all the plugin classes available on the system.
:returns: A dict with plugin entrypoint name as the key and the plugin
class as the value.
:rtype: dict
"""
mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE,
propagate_map_exceptions=True)
return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.plugin)))
def get_plugin_loader(name):
"""Retrieve a plugin class by its entrypoint name.
:param str name: The name of the object to get.
:returns: An auth plugin class.
:rtype: :py:class:`keystoneauth1.loading.BaseLoader`
:raises keystonauth.exceptions.NoMatchingPlugin: if a plugin cannot be
created.
"""
try:
mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE,
name=name)
except RuntimeError:
raise exceptions.NoMatchingPlugin(name)
return mgr.driver
@six.add_metaclass(abc.ABCMeta)
class BaseLoader(object):
@abc.abstractproperty
def plugin_class(self):
raise NotImplemented()
@abc.abstractmethod
def get_options(self):
"""Return the list of parameters associated with the auth plugin.
This list may be used to generate CLI or config arguments.
:returns: A list of Param objects describing available plugin
parameters.
:rtype: list
"""
return []
def load_from_options(self, **kwargs):
"""Create a plugin from the arguments retrieved from get_options.
A client can override this function to do argument validation or to
handle differences between the registered options and what is required
to create the plugin.
"""
return self.plugin_class(**kwargs)
def register_argparse_arguments(self, parser):
"""Register the CLI options provided by a specific plugin.
Given a plugin class convert it's options into argparse arguments and
add them to a parser.
:param parser: the parser to attach argparse options.
:type parser: argparse.ArgumentParser
"""
# NOTE(jamielennox): ideally oslo_config would be smart enough to
# handle all the Opt manipulation that goes on in this file. However it
# is currently not. Options are handled in as similar a way as
# possible to oslo_config such that when available we should be able to
# transition.
for opt in self.get_options():
args = []
envs = []
for o in [opt] + opt.deprecated_opts:
args.append('--os-%s' % o.name)
envs.append('OS_%s' % o.name.replace('-', '_').upper())
# select the first ENV that is not false-y or return None
env_vars = (os.environ.get(e) for e in envs)
default = six.next(six.moves.filter(None, env_vars), None)
parser.add_argument(*args,
default=default or opt.default,
metavar=opt.metavar,
help=opt.help,
dest='os_%s' % opt.dest)
def load_from_argparse_arguments(self, namespace, **kwargs):
"""Load a specific plugin object from an argparse result.
Convert the results of a parse into the specified plugin.
:param namespace: The result from CLI parsing.
:type namespace: argparse.Namespace
:returns: An auth plugin, or None if a name is not provided.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
"""
def _getter(opt):
return getattr(namespace, 'os_%s' % opt.dest)
return self.load_from_options_getter(_getter, **kwargs)
def register_conf_options(self, conf, group):
"""Register the oslo_config options that are needed for a plugin.
:param conf: A config object.
:type conf: oslo_config.cfg.ConfigOpts
:param string group: The group name that options should be read from.
"""
plugin_opts = self.get_options()
conf.register_opts(plugin_opts, group=group)
def load_from_conf_options(self, conf, group, **kwargs):
"""Load the plugin from a CONF object.
Convert the options already registered into a real plugin.
:param conf: A config object.
:type conf: oslo_config.cfg.ConfigOpts
:param string group: The group name that options should be read from.
:returns: An authentication Plugin.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
"""
def _getter(opt):
return conf[group][opt.dest]
return self.load_from_options_getter(_getter, **kwargs)
def load_from_options_getter(self, getter, **kwargs):
"""Load a plugin from a getter function that returns appropriate values
To handle cases other than the provided CONF and CLI loading you can
specify a custom loader function that will be queried for the option
value.
The getter is a function that takes one value, an
:py:class:`oslo_config.cfg.Opt` and returns a value to load with.
:param getter: A function that returns a value for the given opt.
:type getter: callable
:returns: An authentication Plugin.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
"""
plugin_opts = self.get_options()
for opt in plugin_opts:
val = getter(opt)
if val is not None:
val = opt.type(val)
kwargs.setdefault(opt.dest, val)
return self.load_from_options(**kwargs)

View File

@ -14,7 +14,7 @@ import argparse
import os
from keystoneauth1 import _utils as utils
from keystoneauth1 import base
from keystoneauth1.loading import base
@utils.positional()
@ -53,7 +53,7 @@ def register_argparse_arguments(parser, argv, default=None):
plugin = options.os_auth_plugin
else:
msg = 'Options specific to the %s plugin.' % options.os_auth_plugin
plugin = base.get_plugin_class(options.os_auth_plugin)
plugin = base.get_plugin_loader(options.os_auth_plugin)
group = parser.add_argument_group('Authentication Options', msg)
plugin.register_argparse_arguments(group)
@ -80,6 +80,6 @@ def load_from_argparse_arguments(namespace, **kwargs):
if isinstance(namespace.os_auth_plugin, type):
plugin = namespace.os_auth_plugin
else:
plugin = base.get_plugin_class(namespace.os_auth_plugin)
plugin = base.get_plugin_loader(namespace.os_auth_plugin)
return plugin.load_from_argparse_arguments(namespace, **kwargs)

View File

@ -12,7 +12,7 @@
from oslo_config import cfg
from keystoneauth1 import base
from keystoneauth1.loading import base
_AUTH_PLUGIN_OPT = cfg.StrOpt('auth_plugin', help='Name of the plugin to load')
@ -43,7 +43,7 @@ def get_plugin_options(name):
:returns: A list of oslo_config options.
"""
return base.get_plugin_class(name).get_options()
return base.get_plugin_loader(name).get_options()
def register_conf_options(conf, group):
@ -106,6 +106,6 @@ def load_from_conf_options(conf, group, **kwargs):
if not name:
return None
plugin_class = base.get_plugin_class(name)
plugin_class = base.get_plugin_loader(name)
plugin_class.register_conf_options(conf, group)
return plugin_class.load_from_conf_options(conf, group, **kwargs)

View File

@ -10,72 +10,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import six
import stevedore
from keystoneauth1 import exceptions
# NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be
# requested from get_endpoint. If a plugin receives this as the value of
# 'interface' it should return the initial URL that was passed to the plugin.
AUTH_INTERFACE = object()
PLUGIN_NAMESPACE = 'keystoneauth1.auth.plugin'
IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token'
def get_available_plugin_names():
"""Get the names of all the plugins that are available on the system.
This is particularly useful for help and error text to prompt a user for
example what plugins they may specify.
:returns: A list of names.
:rtype: frozenset
"""
mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE,
invoke_on_load=False)
return frozenset(mgr.names())
def get_available_plugin_classes():
"""Retrieve all the plugin classes available on the system.
:returns: A dict with plugin entrypoint name as the key and the plugin
class as the value.
:rtype: dict
"""
mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE,
propagate_map_exceptions=True,
invoke_on_load=False)
return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.plugin)))
def get_plugin_class(name):
"""Retrieve a plugin class by its entrypoint name.
:param str name: The name of the object to get.
:returns: An auth plugin class.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
:raises keystonauth.exceptions.NoMatchingPlugin: if a plugin cannot be
created.
"""
try:
mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE,
name=name,
invoke_on_load=False)
except RuntimeError:
raise exceptions.NoMatchingPlugin(name)
return mgr.driver
class BaseAuthPlugin(object):
"""The basic structure of an authentication plugin."""
@ -255,136 +197,3 @@ class BaseAuthPlugin(object):
"""
return None
@classmethod
def get_options(cls):
"""Return the list of parameters associated with the auth plugin.
This list may be used to generate CLI or config arguments.
:returns: A list of Param objects describing available plugin
parameters.
:rtype: list
"""
return []
@classmethod
def load_from_options(cls, **kwargs):
"""Create a plugin from the arguments retrieved from get_options.
A client can override this function to do argument validation or to
handle differences between the registered options and what is required
to create the plugin.
"""
return cls(**kwargs)
@classmethod
def register_argparse_arguments(cls, parser):
"""Register the CLI options provided by a specific plugin.
Given a plugin class convert it's options into argparse arguments and
add them to a parser.
:param parser: the parser to attach argparse options.
:type parser: argparse.ArgumentParser
"""
# NOTE(jamielennox): ideally oslo_config would be smart enough to
# handle all the Opt manipulation that goes on in this file. However it
# is currently not. Options are handled in as similar a way as
# possible to oslo_config such that when available we should be able to
# transition.
for opt in cls.get_options():
args = []
envs = []
for o in [opt] + opt.deprecated_opts:
args.append('--os-%s' % o.name)
envs.append('OS_%s' % o.name.replace('-', '_').upper())
# select the first ENV that is not false-y or return None
env_vars = (os.environ.get(e) for e in envs)
default = six.next(six.moves.filter(None, env_vars), None)
parser.add_argument(*args,
default=default or opt.default,
metavar=opt.metavar,
help=opt.help,
dest='os_%s' % opt.dest)
@classmethod
def load_from_argparse_arguments(cls, namespace, **kwargs):
"""Load a specific plugin object from an argparse result.
Convert the results of a parse into the specified plugin.
:param namespace: The result from CLI parsing.
:type namespace: argparse.Namespace
:returns: An auth plugin, or None if a name is not provided.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
"""
def _getter(opt):
return getattr(namespace, 'os_%s' % opt.dest)
return cls.load_from_options_getter(_getter, **kwargs)
@classmethod
def register_conf_options(cls, conf, group):
"""Register the oslo_config options that are needed for a plugin.
:param conf: A config object.
:type conf: oslo_config.cfg.ConfigOpts
:param string group: The group name that options should be read from.
"""
plugin_opts = cls.get_options()
conf.register_opts(plugin_opts, group=group)
@classmethod
def load_from_conf_options(cls, conf, group, **kwargs):
"""Load the plugin from a CONF object.
Convert the options already registered into a real plugin.
:param conf: A config object.
:type conf: oslo_config.cfg.ConfigOpts
:param string group: The group name that options should be read from.
:returns: An authentication Plugin.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
"""
def _getter(opt):
return conf[group][opt.dest]
return cls.load_from_options_getter(_getter, **kwargs)
@classmethod
def load_from_options_getter(cls, getter, **kwargs):
"""Load a plugin from a getter function that returns appropriate values
To handle cases other than the provided CONF and CLI loading you can
specify a custom loader function that will be queried for the option
value.
The getter is a function that takes one value, an
:py:class:`oslo_config.cfg.Opt` and returns a value to load with.
:param getter: A function that returns a value for the given opt.
:type getter: callable
:returns: An authentication Plugin.
:rtype: :py:class:`keystonauth.auth.BaseAuthPlugin`
"""
plugin_opts = cls.get_options()
for opt in plugin_opts:
val = getter(opt)
if val is not None:
val = opt.type(val)
kwargs.setdefault(opt.dest, val)
return cls.load_from_options(**kwargs)

View File

@ -13,9 +13,9 @@
import uuid
from keystoneauth1 import access
from keystoneauth1 import base
from keystoneauth1 import fixture
from keystoneauth1.identity import access as access_plugin
from keystoneauth1 import plugin
from keystoneauth1 import session
from keystoneauth1.tests.unit import utils
@ -36,20 +36,20 @@ class AccessInfoPluginTests(utils.TestCase):
return access_plugin.AccessInfoPlugin(auth_ref, **kwargs)
def test_auth_ref(self):
plugin = self._plugin()
plugin_obj = self._plugin()
self.assertEqual(self.TEST_ROOT_URL,
plugin.get_endpoint(self.session,
service_type='identity',
interface='public'))
self.assertEqual(self.auth_token, plugin.get_token(session))
plugin_obj.get_endpoint(self.session,
service_type='identity',
interface='public'))
self.assertEqual(self.auth_token, plugin_obj.get_token(session))
def test_auth_url(self):
auth_url = 'http://keystone.test.url'
plugin = self._plugin(auth_url=auth_url)
obj = self._plugin(auth_url=auth_url)
self.assertEqual(auth_url,
plugin.get_endpoint(self.session,
interface=base.AUTH_INTERFACE))
obj.get_endpoint(self.session,
interface=plugin.AUTH_INTERFACE))
def test_invalidate(self):
plugin = self._plugin()

View File

@ -18,10 +18,10 @@ import six
from keystoneauth1 import _utils
from keystoneauth1 import access
from keystoneauth1 import base
from keystoneauth1 import exceptions
from keystoneauth1 import fixture
from keystoneauth1 import identity
from keystoneauth1 import plugin
from keystoneauth1 import session
from keystoneauth1.tests.unit import utils
@ -187,7 +187,7 @@ class CommonIdentityTests(object):
s = session.Session(auth=a)
auth_url = s.get_endpoint(service_type='compute',
interface=base.AUTH_INTERFACE)
interface=plugin.AUTH_INTERFACE)
self.assertEqual(self.TEST_URL, auth_url)
@ -394,13 +394,13 @@ class CatalogHackTests(utils.TestCase):
sess = session.Session(auth=v2_auth)
endpoint = sess.get_endpoint(interface=base.AUTH_INTERFACE,
endpoint = sess.get_endpoint(interface=plugin.AUTH_INTERFACE,
version=(3, 0))
self.assertEqual(self.V3_URL, endpoint)
class GenericPlugin(base.BaseAuthPlugin):
class GenericPlugin(plugin.BaseAuthPlugin):
BAD_TOKEN = uuid.uuid4().hex