From efcadd69373e6954946a61f41c5b0a66bd99875a Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 3 Aug 2015 09:32:56 +1000 Subject: [PATCH] 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 --- keystoneauth1/__init__.py | 38 ---- keystoneauth1/identity/base.py | 19 +- keystoneauth1/identity/generic/base.py | 23 -- keystoneauth1/identity/generic/password.py | 19 -- keystoneauth1/identity/generic/token.py | 14 -- keystoneauth1/identity/v2.py | 38 ---- keystoneauth1/identity/v3/base.py | 19 -- keystoneauth1/identity/v3/federation.py | 14 -- keystoneauth1/identity/v3/k2k.py | 16 +- keystoneauth1/identity/v3/password.py | 17 -- keystoneauth1/identity/v3/token.py | 14 -- keystoneauth1/loading/__init__.py | 35 +++ keystoneauth1/loading/_plugins/__init__.py | 0 .../loading/_plugins/identity/__init__.py | 0 .../loading/_plugins/identity/base.py | 27 +++ .../loading/_plugins/identity/generic.py | 75 +++++++ keystoneauth1/loading/_plugins/identity/v2.py | 67 ++++++ keystoneauth1/loading/_plugins/identity/v3.py | 92 ++++++++ .../loading/_plugins/token_endpoint.py | 36 ++++ keystoneauth1/loading/base.py | 202 ++++++++++++++++++ keystoneauth1/{ => loading}/cli.py | 6 +- keystoneauth1/{ => loading}/conf.py | 6 +- keystoneauth1/{base.py => plugin.py} | 191 ----------------- keystoneauth1/tests/unit/auth/test_access.py | 18 +- .../tests/unit/auth/test_identity_common.py | 8 +- .../unit/auth/test_identity_v3_federation.py | 10 - keystoneauth1/tests/unit/auth/test_loading.py | 2 +- .../tests/unit/auth/test_password.py | 3 +- keystoneauth1/tests/unit/auth/test_token.py | 3 +- .../tests/unit/auth/test_token_endpoint.py | 3 +- keystoneauth1/tests/unit/auth/utils.py | 37 ++-- .../tests/unit/{auth => loading}/test_cli.py | 54 +++-- .../tests/unit/{auth => loading}/test_conf.py | 53 +++-- keystoneauth1/tests/unit/test_session.py | 6 +- keystoneauth1/token_endpoint.py | 20 +- setup.cfg | 15 +- 36 files changed, 656 insertions(+), 544 deletions(-) create mode 100644 keystoneauth1/loading/__init__.py create mode 100644 keystoneauth1/loading/_plugins/__init__.py create mode 100644 keystoneauth1/loading/_plugins/identity/__init__.py create mode 100644 keystoneauth1/loading/_plugins/identity/base.py create mode 100644 keystoneauth1/loading/_plugins/identity/generic.py create mode 100644 keystoneauth1/loading/_plugins/identity/v2.py create mode 100644 keystoneauth1/loading/_plugins/identity/v3.py create mode 100644 keystoneauth1/loading/_plugins/token_endpoint.py create mode 100644 keystoneauth1/loading/base.py rename keystoneauth1/{ => loading}/cli.py (94%) rename keystoneauth1/{ => loading}/conf.py (96%) rename keystoneauth1/{base.py => plugin.py} (54%) rename keystoneauth1/tests/unit/{auth => loading}/test_cli.py (77%) rename keystoneauth1/tests/unit/{auth => loading}/test_conf.py (74%) diff --git a/keystoneauth1/__init__.py b/keystoneauth1/__init__.py index cfa71697..e69de29b 100644 --- a/keystoneauth1/__init__.py +++ b/keystoneauth1/__init__.py @@ -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', -] diff --git a/keystoneauth1/identity/base.py b/keystoneauth1/identity/base.py index 8fee3ec6..a5bb1674 100644 --- a/keystoneauth1/identity/base.py +++ b/keystoneauth1/identity/base.py @@ -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 diff --git a/keystoneauth1/identity/generic/base.py b/keystoneauth1/identity/generic/base.py index e4e8c6da..a71491af 100644 --- a/keystoneauth1/identity/generic/base.py +++ b/keystoneauth1/identity/generic/base.py @@ -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 diff --git a/keystoneauth1/identity/generic/password.py b/keystoneauth1/identity/generic/password.py index c5094293..daf2e63c 100644 --- a/keystoneauth1/identity/generic/password.py +++ b/keystoneauth1/identity/generic/password.py @@ -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 diff --git a/keystoneauth1/identity/generic/token.py b/keystoneauth1/identity/generic/token.py index 635563f9..7bca2e66 100644 --- a/keystoneauth1/identity/generic/token.py +++ b/keystoneauth1/identity/generic/token.py @@ -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 diff --git a/keystoneauth1/identity/v2.py b/keystoneauth1/identity/v2.py index 3f6e8968..e3d77ccb 100644 --- a/keystoneauth1/identity/v2.py +++ b/keystoneauth1/identity/v2.py @@ -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 diff --git a/keystoneauth1/identity/v3/base.py b/keystoneauth1/identity/v3/base.py index c4849f7c..94967f02 100644 --- a/keystoneauth1/identity/v3/base.py +++ b/keystoneauth1/identity/v3/base.py @@ -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. diff --git a/keystoneauth1/identity/v3/federation.py b/keystoneauth1/identity/v3/federation.py index 4c6e1509..ad97ba36 100644 --- a/keystoneauth1/identity/v3/federation.py +++ b/keystoneauth1/identity/v3/federation.py @@ -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.""" diff --git a/keystoneauth1/identity/v3/k2k.py b/keystoneauth1/identity/v3/k2k.py index 0acd2f24..e2cae750 100644 --- a/keystoneauth1/identity/v3/k2k.py +++ b/keystoneauth1/identity/v3/k2k.py @@ -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, diff --git a/keystoneauth1/identity/v3/password.py b/keystoneauth1/identity/v3/password.py index 100ce765..b89ce843 100644 --- a/keystoneauth1/identity/v3/password.py +++ b/keystoneauth1/identity/v3/password.py @@ -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 diff --git a/keystoneauth1/identity/v3/token.py b/keystoneauth1/identity/v3/token.py index 89e7f89c..e9e08414 100644 --- a/keystoneauth1/identity/v3/token.py +++ b/keystoneauth1/identity/v3/token.py @@ -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 diff --git a/keystoneauth1/loading/__init__.py b/keystoneauth1/loading/__init__.py new file mode 100644 index 00000000..8ae25684 --- /dev/null +++ b/keystoneauth1/loading/__init__.py @@ -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', +] diff --git a/keystoneauth1/loading/_plugins/__init__.py b/keystoneauth1/loading/_plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keystoneauth1/loading/_plugins/identity/__init__.py b/keystoneauth1/loading/_plugins/identity/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keystoneauth1/loading/_plugins/identity/base.py b/keystoneauth1/loading/_plugins/identity/base.py new file mode 100644 index 00000000..9b9e5318 --- /dev/null +++ b/keystoneauth1/loading/_plugins/identity/base.py @@ -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 diff --git a/keystoneauth1/loading/_plugins/identity/generic.py b/keystoneauth1/loading/_plugins/identity/generic.py new file mode 100644 index 00000000..049248fd --- /dev/null +++ b/keystoneauth1/loading/_plugins/identity/generic.py @@ -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 diff --git a/keystoneauth1/loading/_plugins/identity/v2.py b/keystoneauth1/loading/_plugins/identity/v2.py new file mode 100644 index 00000000..da7f77bc --- /dev/null +++ b/keystoneauth1/loading/_plugins/identity/v2.py @@ -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 diff --git a/keystoneauth1/loading/_plugins/identity/v3.py b/keystoneauth1/loading/_plugins/identity/v3.py new file mode 100644 index 00000000..a311665a --- /dev/null +++ b/keystoneauth1/loading/_plugins/identity/v3.py @@ -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 diff --git a/keystoneauth1/loading/_plugins/token_endpoint.py b/keystoneauth1/loading/_plugins/token_endpoint.py new file mode 100644 index 00000000..361408ca --- /dev/null +++ b/keystoneauth1/loading/_plugins/token_endpoint.py @@ -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 diff --git a/keystoneauth1/loading/base.py b/keystoneauth1/loading/base.py new file mode 100644 index 00000000..588fa5cf --- /dev/null +++ b/keystoneauth1/loading/base.py @@ -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) diff --git a/keystoneauth1/cli.py b/keystoneauth1/loading/cli.py similarity index 94% rename from keystoneauth1/cli.py rename to keystoneauth1/loading/cli.py index b27299c9..b124a295 100644 --- a/keystoneauth1/cli.py +++ b/keystoneauth1/loading/cli.py @@ -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) diff --git a/keystoneauth1/conf.py b/keystoneauth1/loading/conf.py similarity index 96% rename from keystoneauth1/conf.py rename to keystoneauth1/loading/conf.py index 1a7b9c25..4f316b43 100644 --- a/keystoneauth1/conf.py +++ b/keystoneauth1/loading/conf.py @@ -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) diff --git a/keystoneauth1/base.py b/keystoneauth1/plugin.py similarity index 54% rename from keystoneauth1/base.py rename to keystoneauth1/plugin.py index 6b85973a..80e1999e 100644 --- a/keystoneauth1/base.py +++ b/keystoneauth1/plugin.py @@ -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) diff --git a/keystoneauth1/tests/unit/auth/test_access.py b/keystoneauth1/tests/unit/auth/test_access.py index 1c1803ce..15eaba30 100644 --- a/keystoneauth1/tests/unit/auth/test_access.py +++ b/keystoneauth1/tests/unit/auth/test_access.py @@ -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() diff --git a/keystoneauth1/tests/unit/auth/test_identity_common.py b/keystoneauth1/tests/unit/auth/test_identity_common.py index dfc2dd83..85d45177 100644 --- a/keystoneauth1/tests/unit/auth/test_identity_common.py +++ b/keystoneauth1/tests/unit/auth/test_identity_common.py @@ -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 diff --git a/keystoneauth1/tests/unit/auth/test_identity_v3_federation.py b/keystoneauth1/tests/unit/auth/test_identity_v3_federation.py index 55570757..714bc3e4 100644 --- a/keystoneauth1/tests/unit/auth/test_identity_v3_federation.py +++ b/keystoneauth1/tests/unit/auth/test_identity_v3_federation.py @@ -93,12 +93,6 @@ class V3FederatedPlugin(utils.TestCase): self.assertTrue(self.unscoped_mock.called) self.assertTrue(self.scoped_mock.called) - def test_options(self): - opts = [o.name for o in v3.FederationBaseAuth.get_options()] - - self.assertIn('protocol', opts) - self.assertIn('identity-provider', opts) - class K2KAuthPluginTest(utils.TestCase): @@ -167,10 +161,6 @@ class K2KAuthPluginTest(utils.TestCase): kwargs.setdefault('service_provider', self.SP_ID) return v3.Keystone2Keystone(**kwargs) - def test_options(self): - opts = [o.name for o in v3.Keystone2Keystone.get_options()] - self.assertIn('service-provider', opts) - def test_remote_url(self): remote_auth_url = self.k2kplugin._remote_auth_url(self.SP_AUTH_URL) self.assertEqual(self.SP_ROOT_URL, remote_auth_url) diff --git a/keystoneauth1/tests/unit/auth/test_loading.py b/keystoneauth1/tests/unit/auth/test_loading.py index 382a68ca..2b90bbb1 100644 --- a/keystoneauth1/tests/unit/auth/test_loading.py +++ b/keystoneauth1/tests/unit/auth/test_loading.py @@ -35,7 +35,7 @@ class TestOtherLoading(utils.TestCase): # return str because oslo.config should convert them back return str(vals[opt.name]) - p = utils.MockPlugin.load_from_options_getter(_getter, other=val) + p = utils.MockLoader().load_from_options_getter(_getter, other=val) self.assertEqual(set(vals), set(called_opts)) diff --git a/keystoneauth1/tests/unit/auth/test_password.py b/keystoneauth1/tests/unit/auth/test_password.py index 22b4e866..08e62fc3 100644 --- a/keystoneauth1/tests/unit/auth/test_password.py +++ b/keystoneauth1/tests/unit/auth/test_password.py @@ -16,6 +16,7 @@ from keystoneauth1.identity.generic import password from keystoneauth1.identity import v2 from keystoneauth1.identity import v3 from keystoneauth1.identity.v3 import password as v3_password +from keystoneauth1.loading._plugins.identity import generic from keystoneauth1.tests.unit.auth import utils @@ -41,7 +42,7 @@ class PasswordTests(utils.GenericPluginTestCase): self.assertDiscoveryFailure(user_domain_id=uuid.uuid4().hex) def test_options(self): - opts = [o.name for o in self.PLUGIN_CLASS.get_options()] + opts = [o.name for o in generic.Password().get_options()] allowed_opts = ['user-name', 'user-domain-id', diff --git a/keystoneauth1/tests/unit/auth/test_token.py b/keystoneauth1/tests/unit/auth/test_token.py index 25a717c6..ecb8f4c7 100644 --- a/keystoneauth1/tests/unit/auth/test_token.py +++ b/keystoneauth1/tests/unit/auth/test_token.py @@ -16,6 +16,7 @@ from keystoneauth1.identity.generic import token from keystoneauth1.identity import v2 from keystoneauth1.identity import v3 from keystoneauth1.identity.v3 import token as v3_token +from keystoneauth1.loading._plugins.identity import generic from keystoneauth1.tests.unit.auth import utils @@ -30,7 +31,7 @@ class TokenTests(utils.GenericPluginTestCase): return super(TokenTests, self).new_plugin(**kwargs) def test_options(self): - opts = [o.name for o in self.PLUGIN_CLASS.get_options()] + opts = [o.name for o in generic.Token().get_options()] allowed_opts = ['token', 'domain-id', diff --git a/keystoneauth1/tests/unit/auth/test_token_endpoint.py b/keystoneauth1/tests/unit/auth/test_token_endpoint.py index 0824c9b1..9b2417d9 100644 --- a/keystoneauth1/tests/unit/auth/test_token_endpoint.py +++ b/keystoneauth1/tests/unit/auth/test_token_endpoint.py @@ -12,6 +12,7 @@ from testtools import matchers +from keystoneauth1.loading._plugins import token_endpoint as loader from keystoneauth1 import session from keystoneauth1.tests.unit import utils from keystoneauth1 import token_endpoint @@ -47,7 +48,7 @@ class TokenEndpointTest(utils.TestCase): self.assertRequestHeaderEqual('X-Auth-Token', self.TEST_TOKEN) def test_token_endpoint_options(self): - opt_names = [opt.name for opt in token_endpoint.Token.get_options()] + opt_names = [opt.name for opt in loader.TokenEndpoint().get_options()] self.assertThat(opt_names, matchers.HasLength(2)) diff --git a/keystoneauth1/tests/unit/auth/utils.py b/keystoneauth1/tests/unit/auth/utils.py index a4a363fb..4dd86558 100644 --- a/keystoneauth1/tests/unit/auth/utils.py +++ b/keystoneauth1/tests/unit/auth/utils.py @@ -18,20 +18,15 @@ from oslo_config import cfg import six from keystoneauth1 import access -from keystoneauth1 import base from keystoneauth1 import exceptions from keystoneauth1 import fixture +from keystoneauth1 import loading +from keystoneauth1 import plugin from keystoneauth1 import session from keystoneauth1.tests.unit import utils -class MockPlugin(base.BaseAuthPlugin): - - INT_DESC = 'test int' - FLOAT_DESC = 'test float' - BOOL_DESC = 'test bool' - STR_DESC = 'test str' - STR_DEFAULT = uuid.uuid4().hex +class MockPlugin(plugin.BaseAuthPlugin): def __init__(self, **kwargs): self._data = kwargs @@ -45,13 +40,25 @@ class MockPlugin(base.BaseAuthPlugin): def get_endpoint(self, *args, **kwargs): return 'http://test' - @classmethod - def get_options(cls): + +class MockLoader(loading.BaseLoader): + + INT_DESC = 'test int' + FLOAT_DESC = 'test float' + BOOL_DESC = 'test bool' + STR_DESC = 'test str' + STR_DEFAULT = uuid.uuid4().hex + + @property + def plugin_class(self): + return MockPlugin + + def get_options(self): return [ - cfg.IntOpt('a-int', default='3', help=cls.INT_DESC), - cfg.BoolOpt('a-bool', help=cls.BOOL_DESC), - cfg.FloatOpt('a-float', help=cls.FLOAT_DESC), - cfg.StrOpt('a-str', help=cls.STR_DESC, default=cls.STR_DEFAULT), + cfg.IntOpt('a-int', default='3', help=self.INT_DESC), + cfg.BoolOpt('a-bool', help=self.BOOL_DESC), + cfg.FloatOpt('a-float', help=self.FLOAT_DESC), + cfg.StrOpt('a-str', help=self.STR_DESC, default=self.STR_DEFAULT), ] @@ -64,7 +71,7 @@ class MockManager(object): def mock_plugin(f): @functools.wraps(f) def inner(*args, **kwargs): - with mock.patch.object(base, 'get_plugin_class') as m: + with mock.patch.object(loading, 'get_plugin_loader') as m: m.return_value = MockPlugin args = list(args) + [m] return f(*args, **kwargs) diff --git a/keystoneauth1/tests/unit/auth/test_cli.py b/keystoneauth1/tests/unit/loading/test_cli.py similarity index 77% rename from keystoneauth1/tests/unit/auth/test_cli.py rename to keystoneauth1/tests/unit/loading/test_cli.py index 912dcfed..11f3369c 100644 --- a/keystoneauth1/tests/unit/auth/test_cli.py +++ b/keystoneauth1/tests/unit/loading/test_cli.py @@ -18,7 +18,7 @@ import mock from oslo_config import cfg from keystoneauth1 import base -from keystoneauth1 import cli +from keystoneauth1 import loading from keystoneauth1.tests.unit.auth import utils @@ -27,8 +27,14 @@ class TesterPlugin(base.BaseAuthPlugin): def get_token(self, *args, **kwargs): return None - @classmethod - def get_options(cls): + +class TesterLoader(loading.BaseLoader): + + @property + def plugin_class(self): + return TesterPlugin + + def get_options(self): # NOTE(jamielennox): this is kind of horrible. If you specify this as # a deprecated_name= value it will convert - to _ which is not what we # want for a CLI option. @@ -52,20 +58,20 @@ class CliTests(utils.TestCase): return self.useFixture(fixtures.EnvironmentVariable(name, value)) def test_creating_with_no_args(self): - ret = cli.register_argparse_arguments(self.p, []) + ret = loading.register_argparse_arguments(self.p, []) self.assertIsNone(ret) self.assertIn('--os-auth-plugin', self.p.format_usage()) def test_load_with_nothing(self): - cli.register_argparse_arguments(self.p, []) + loading.register_argparse_arguments(self.p, []) opts = self.p.parse_args([]) - self.assertIsNone(cli.load_from_argparse_arguments(opts)) + self.assertIsNone(loading.load_from_argparse_arguments(opts)) @utils.mock_plugin def test_basic_params_added(self, m): name = uuid.uuid4().hex argv = ['--os-auth-plugin', name] - ret = cli.register_argparse_arguments(self.p, argv) + ret = loading.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, ret) for n in ('--os-a-int', '--os-a-bool', '--os-a-float'): @@ -81,13 +87,13 @@ class CliTests(utils.TestCase): '--os-a-float', str(self.a_float), '--os-a-bool', str(self.a_bool)] - klass = cli.register_argparse_arguments(self.p, argv) + klass = loading.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, klass) opts = self.p.parse_args(argv) self.assertEqual(name, opts.os_auth_plugin) - a = cli.load_from_argparse_arguments(opts) + a = loading.load_from_argparse_arguments(opts) self.assertTestVals(a) self.assertEqual(name, opts.os_auth_plugin) @@ -101,13 +107,13 @@ class CliTests(utils.TestCase): argv = ['--os-auth-plugin', name, '--os-a-float', str(self.a_float)] - klass = cli.register_argparse_arguments(self.p, argv) + klass = loading.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, klass) opts = self.p.parse_args(argv) self.assertEqual(name, opts.os_auth_plugin) - a = cli.load_from_argparse_arguments(opts) + a = loading.load_from_argparse_arguments(opts) self.assertEqual(self.a_float, a['a_float']) self.assertEqual(3, a['a_int']) @@ -115,7 +121,7 @@ class CliTests(utils.TestCase): @utils.mock_plugin def test_with_default_string_value(self, m): name = uuid.uuid4().hex - klass = cli.register_argparse_arguments(self.p, [], default=name) + klass = loading.register_argparse_arguments(self.p, [], default=name) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @@ -124,14 +130,17 @@ class CliTests(utils.TestCase): name = uuid.uuid4().hex default = uuid.uuid4().hex argv = ['--os-auth-plugin', name] - klass = cli.register_argparse_arguments(self.p, argv, default=default) + klass = loading.register_argparse_arguments(self.p, + argv, + default=default) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @utils.mock_plugin def test_with_default_type_value(self, m): - klass = cli.register_argparse_arguments(self.p, [], - default=utils.MockPlugin) + klass = loading.register_argparse_arguments(self.p, + [], + default=utils.MockPlugin) self.assertIs(utils.MockPlugin, klass) self.assertEqual(0, m.call_count) @@ -143,8 +152,9 @@ class CliTests(utils.TestCase): pass name = uuid.uuid4().hex argv = ['--os-auth-plugin', name] - klass = cli.register_argparse_arguments(self.p, argv, - default=TestPlugin) + klass = loading.register_argparse_arguments(self.p, + argv, + default=TestPlugin) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @@ -154,20 +164,20 @@ class CliTests(utils.TestCase): val = uuid.uuid4().hex self.env('OS_A_STR', val) - klass = cli.register_argparse_arguments(self.p, [], default=name) + klass = loading.register_argparse_arguments(self.p, [], default=name) opts = self.p.parse_args([]) a = klass.load_from_argparse_arguments(opts) self.assertEqual(val, a['a_str']) def test_deprecated_cli_options(self): - TesterPlugin.register_argparse_arguments(self.p) + TesterLoader().register_argparse_arguments(self.p) val = uuid.uuid4().hex opts = self.p.parse_args(['--os-test-other', val]) self.assertEqual(val, opts.os_test_opt) def test_deprecated_multi_cli_options(self): - TesterPlugin.register_argparse_arguments(self.p) + TesterLoader().register_argparse_arguments(self.p) val1 = uuid.uuid4().hex val2 = uuid.uuid4().hex # argarse rules say that the last specified wins. @@ -179,7 +189,7 @@ class CliTests(utils.TestCase): val = uuid.uuid4().hex with mock.patch.dict('os.environ', {'OS_TEST_OTHER': val}): - TesterPlugin.register_argparse_arguments(self.p) + TesterLoader().register_argparse_arguments(self.p) opts = self.p.parse_args([]) self.assertEqual(val, opts.os_test_opt) @@ -190,7 +200,7 @@ class CliTests(utils.TestCase): with mock.patch.dict('os.environ', {'OS_TEST_OPT': val1, 'OS_TEST_OTHER': val2}): - TesterPlugin.register_argparse_arguments(self.p) + TesterLoader().register_argparse_arguments(self.p) opts = self.p.parse_args([]) self.assertEqual(val1, opts.os_test_opt) diff --git a/keystoneauth1/tests/unit/auth/test_conf.py b/keystoneauth1/tests/unit/loading/test_conf.py similarity index 74% rename from keystoneauth1/tests/unit/auth/test_conf.py rename to keystoneauth1/tests/unit/loading/test_conf.py index 9f49ad5e..48a857d1 100644 --- a/keystoneauth1/tests/unit/auth/test_conf.py +++ b/keystoneauth1/tests/unit/loading/test_conf.py @@ -17,11 +17,10 @@ from oslo_config import cfg from oslo_config import fixture as config import stevedore -from keystoneauth1 import base -from keystoneauth1 import conf from keystoneauth1 import exceptions -from keystoneauth1.identity import v2 as v2_auth -from keystoneauth1.identity import v3 as v3_auth +from keystoneauth1 import loading +from keystoneauth1.loading._plugins.identity import v2 +from keystoneauth1.loading._plugins.identity import v3 from keystoneauth1.tests.unit.auth import utils @@ -35,7 +34,7 @@ class ConfTests(utils.TestCase): # we need them in place before we can stub them. We will need to run # the register again after we stub the auth section and auth plugin so # it can load the plugin specific options. - conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) + loading.register_conf_options(self.conf_fixture.conf, group=self.GROUP) def test_loading_v2(self): section = uuid.uuid4().hex @@ -45,9 +44,9 @@ class ConfTests(utils.TestCase): tenant_id = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) - conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) + loading.register_conf_options(self.conf_fixture.conf, group=self.GROUP) - self.conf_fixture.register_opts(v2_auth.Password.get_options(), + self.conf_fixture.register_opts(v2.Password.get_options(), group=section) self.conf_fixture.config(auth_plugin=self.V2PASS, @@ -57,7 +56,7 @@ class ConfTests(utils.TestCase): tenant_id=tenant_id, group=section) - a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) + a = loading.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertEqual(username, a.username) self.assertEqual(password, a.password) @@ -72,9 +71,9 @@ class ConfTests(utils.TestCase): project_domain_name = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) - conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) + loading.register_conf_options(self.conf_fixture.conf, group=self.GROUP) - self.conf_fixture.register_opts(v3_auth.Token.get_options(), + self.conf_fixture.register_opts(v3.Token().get_options(), group=section) self.conf_fixture.config(auth_plugin=self.V3TOKEN, @@ -84,7 +83,7 @@ class ConfTests(utils.TestCase): project_domain_name=project_domain_name, group=section) - a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) + a = loading.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertEqual(token, a.auth_methods[0].token) self.assertEqual(trust_id, a.trust_id) @@ -97,15 +96,15 @@ class ConfTests(utils.TestCase): group=self.GROUP) e = self.assertRaises(exceptions.NoMatchingPlugin, - conf.load_from_conf_options, + loading.load_from_conf_options, self.conf_fixture.conf, self.GROUP) self.assertEqual(auth_plugin, e.name) def test_loading_with_no_data(self): - self.assertIsNone(conf.load_from_conf_options(self.conf_fixture.conf, - self.GROUP)) + l = loading.load_from_conf_options(self.conf_fixture.conf, self.GROUP) + self.assertIsNone(l) @mock.patch('stevedore.DriverManager') def test_other_params(self, m): @@ -118,23 +117,22 @@ class ConfTests(utils.TestCase): group=self.GROUP, **self.TEST_VALS) - a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) + a = loading.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) - m.assert_called_once_with(namespace=base.PLUGIN_NAMESPACE, - name=driver_name, - invoke_on_load=False) + m.assert_called_once_with(namespace=loading.PLUGIN_NAMESPACE, + name=driver_name) @utils.mock_plugin def test_same_section(self, m): self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=self.GROUP) - conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) + loading.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.config(auth_plugin=uuid.uuid4().hex, group=self.GROUP, **self.TEST_VALS) - a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) + a = loading.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) @utils.mock_plugin @@ -142,7 +140,7 @@ class ConfTests(utils.TestCase): section = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) - conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) + loading.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=section) @@ -150,28 +148,27 @@ class ConfTests(utils.TestCase): auth_plugin=uuid.uuid4().hex, **self.TEST_VALS) - a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) + a = loading.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) def test_plugins_are_all_opts(self): - manager = stevedore.ExtensionManager(base.PLUGIN_NAMESPACE, - invoke_on_load=False, + manager = stevedore.ExtensionManager(loading.PLUGIN_NAMESPACE, propagate_map_exceptions=True) def inner(driver): - for p in driver.plugin.get_options(): + for p in driver.plugin().get_options(): self.assertIsInstance(p, cfg.Opt) manager.map(inner) def test_get_common(self): - opts = conf.get_common_conf_options() + opts = loading.get_common_conf_options() for opt in opts: self.assertIsInstance(opt, cfg.Opt) self.assertEqual(2, len(opts)) def test_get_named(self): - loaded_opts = conf.get_plugin_options('v2password') - plugin_opts = v2_auth.Password.get_options() + loaded_opts = loading.get_plugin_options('v2password') + plugin_opts = v2.Password.get_options() self.assertEqual(plugin_opts, loaded_opts) diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py index 1ac50e1a..8c499ace 100644 --- a/keystoneauth1/tests/unit/test_session.py +++ b/keystoneauth1/tests/unit/test_session.py @@ -24,8 +24,8 @@ import six from testtools import matchers from keystoneauth1 import adapter -from keystoneauth1 import base from keystoneauth1 import exceptions +from keystoneauth1 import plugin from keystoneauth1 import session as client_session from keystoneauth1.tests.unit import utils @@ -320,7 +320,7 @@ class RedirectTests(utils.TestCase): self.assertEqual(r.status_code, s.status_code) -class AuthPlugin(base.BaseAuthPlugin): +class AuthPlugin(plugin.BaseAuthPlugin): """Very simple debug authentication plugin. Takes Parameters such that it can throw exceptions at the right times. @@ -363,7 +363,7 @@ class AuthPlugin(base.BaseAuthPlugin): return self.TEST_PROJECT_ID -class CalledAuthPlugin(base.BaseAuthPlugin): +class CalledAuthPlugin(plugin.BaseAuthPlugin): ENDPOINT = 'http://fakeendpoint/' diff --git a/keystoneauth1/token_endpoint.py b/keystoneauth1/token_endpoint.py index 642c26d8..4f049f9a 100644 --- a/keystoneauth1/token_endpoint.py +++ b/keystoneauth1/token_endpoint.py @@ -10,12 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_config import cfg - -from keystoneauth1 import base +from keystoneauth1 import plugin -class Token(base.BaseAuthPlugin): +class Token(plugin.BaseAuthPlugin): """A provider that will always use the given token and endpoint. This is really only useful for testing and in certain CLI cases where you @@ -38,17 +36,3 @@ class Token(base.BaseAuthPlugin): parameters passed to the plugin. """ return self.endpoint - - @classmethod - def get_options(cls): - options = super(Token, cls).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 diff --git a/setup.cfg b/setup.cfg index 92869ccb..cebff3b1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,14 +25,13 @@ packages = [entry_points] -keystoneauth1.auth.plugin = - password = keystoneauth1.identity.generic:Password - token = keystoneauth1.identity.generic:Token - v2password = keystoneauth1.identity.v2:Password - v2token = keystoneauth1.identity.v2:Token - v3password = keystoneauth1.identity.v3:Password - v3token = keystoneauth1.identity.v3:Token - k2k = keystoneauth1.identity.v3:Keystone2Keystone +keystoneauth1.plugin = + password = keystoneauth1.loading._plugins.identity.generic:Password + token = keystoneauth1.loading._plugins.identity.generic:Token + v2password = keystoneauth1.loading._plugins.identity.v2:Password + v2token = keystoneauth1.loading._plugins.identity.v2:Token + v3password = keystoneauth1.loading._plugins.identity.v3:Password + v3token = keystoneauth1.loading._plugins.identity.v3:Token [build_sphinx] source-dir = doc/source