diff --git a/doc/source/api.rst b/doc/source/api.rst index ded67ed60..3b179e49b 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -19,22 +19,28 @@ If you prefer string value, you can use ``1.1`` (deprecated now), ``2`` or ``2.X`` (where X is a microversion). -Alternatively, you can create a client instance using the keystoneclient +Alternatively, you can create a client instance using the keystoneauth session API:: - >>> from keystoneclient.auth.identity import v2 - >>> from keystoneclient import session + >>> from keystoneauth1 import loading + >>> from keystoneauth1 import session >>> from novaclient import client - >>> auth = v2.Password(auth_url=AUTH_URL, - ... username=USERNAME, - ... password=PASSWORD, - ... tenant_name=PROJECT_ID) + >>> loader = loading.get_plugin_loader('password') + >>> auth = loader.Password(auth_url=AUTH_URL, + ... username=USERNAME, + ... password=PASSWORD, + ... project_id=PROJECT_ID) >>> sess = session.Session(auth=auth) >>> nova = client.Client(VERSION, session=sess) -For more information on this keystoneclient API, see `Using Sessions`_. +If you have PROJECT_NAME instead of a PROJECT_ID, use the project_name +parameter. Similarly, if your cloud uses keystone v3 and you have a DOMAIN_NAME +or DOMAIN_ID, provide it as `user_domain_(name|id)` and if you are using a +PROJECT_NAME also provide the domain information as `project_domain_(name|id)`. -.. _Using Sessions: http://docs.openstack.org/developer/python-keystoneclient/using-sessions.html +For more information on this keystoneauth API, see `Using Sessions`_. + +.. _Using Sessions: http://docs.openstack.org/developer/keystoneauth/using-sessions.html It is also possible to use an instance as a context manager in which case there will be a session kept alive for the duration of the with statement:: diff --git a/novaclient/client.py b/novaclient/client.py index 889d09224..11190687d 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -32,8 +32,8 @@ import pkgutil import re import warnings -from keystoneclient import adapter -from keystoneclient import session +from keystoneauth1 import adapter +from keystoneauth1 import session from oslo_utils import importutils from oslo_utils import netutils import pkg_resources @@ -80,7 +80,7 @@ class SessionClient(adapter.LegacyJsonAdapter): kwargs.setdefault('headers', kwargs.get('headers', {})) api_versions.update_headers(kwargs["headers"], self.api_version) # NOTE(jamielennox): The standard call raises errors from - # keystoneclient, where we need to raise the novaclient errors. + # keystoneauth1, where we need to raise the novaclient errors. raise_exc = kwargs.pop('raise_exc', True) with utils.record_time(self.times, self.timings, method, url): resp, body = super(SessionClient, self).request(url, @@ -680,6 +680,8 @@ def _construct_http_client(username=None, password=None, project_id=None, user_id=None, connection_pool=False, session=None, auth=None, user_agent='python-novaclient', interface=None, api_version=None, **kwargs): + # TODO(mordred): If not session, just make a Session, then return + # SessionClient always if session: return SessionClient(session=session, auth=auth, @@ -806,7 +808,7 @@ def Client(version, *args, **kwargs): (where X is a microversion). - Alternatively, you can create a client instance using the keystoneclient + Alternatively, you can create a client instance using the keystoneauth session API. See "The novaclient Python API" page at python-novaclient's doc. """ diff --git a/novaclient/exceptions.py b/novaclient/exceptions.py index ede19bd8c..cbe701083 100644 --- a/novaclient/exceptions.py +++ b/novaclient/exceptions.py @@ -231,7 +231,7 @@ _code_map = dict((c.http_status, c) for c in _error_classes) class InvalidUsage(RuntimeError): """This function call is invalid in the way you are using this client. - Due to the transition to using keystoneclient some function calls are no + Due to the transition to using keystoneauth some function calls are no longer available. You should make a similar call to the session object instead. """ diff --git a/novaclient/shell.py b/novaclient/shell.py index 39bef130d..1a1c22d35 100644 --- a/novaclient/shell.py +++ b/novaclient/shell.py @@ -23,11 +23,9 @@ import argparse import getpass import logging import sys +import warnings -from keystoneclient.auth.identity.generic import password -from keystoneclient.auth.identity.generic import token -from keystoneclient.auth.identity import v3 as identity -from keystoneclient import session as ksession +from keystoneauth1 import loading from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils @@ -246,23 +244,33 @@ class NovaClientArgumentParser(argparse.ArgumentParser): class OpenStackComputeShell(object): times = [] - def _append_global_identity_args(self, parser): + def _append_global_identity_args(self, parser, argv): # Register the CLI arguments that have moved to the session object. - ksession.Session.register_cli_options(parser) + loading.register_session_argparse_arguments(parser) + # Peek into argv to see if os-auth-token or os-token were given, + # in which case, the token auth plugin is what the user wants + # else, we'll default to password + default_auth_plugin = 'password' + if 'os-token' in argv: + default_auth_plugin = 'token' + loading.register_auth_argparse_arguments( + parser, argv, default=default_auth_plugin) parser.set_defaults(insecure=cliutils.env('NOVACLIENT_INSECURE', default=False)) - - identity.Password.register_argparse_arguments(parser) + parser.set_defaults(os_auth_url=cliutils.env('OS_AUTH_URL', + 'NOVA_URL')) parser.set_defaults(os_username=cliutils.env('OS_USERNAME', 'NOVA_USERNAME')) parser.set_defaults(os_password=cliutils.env('OS_PASSWORD', 'NOVA_PASSWORD')) - parser.set_defaults(os_auth_url=cliutils.env('OS_AUTH_URL', - 'NOVA_URL')) + parser.set_defaults(os_project_name=cliutils.env( + 'OS_PROJECT_NAME', 'OS_TENANT_NAME', 'NOVA_PROJECT_ID')) + parser.set_defaults(os_project_id=cliutils.env( + 'OS_PROJECT_ID', 'OS_TENANT_ID')) - def get_base_parser(self): + def get_base_parser(self, argv): parser = NovaClientArgumentParser( prog='nova', description=__doc__.strip(), @@ -305,8 +313,7 @@ class OpenStackComputeShell(object): parser.add_argument( '--os-auth-token', - default=cliutils.env('OS_AUTH_TOKEN'), - help='Defaults to env[OS_AUTH_TOKEN].') + help=argparse.SUPPRESS) parser.add_argument( '--os_username', @@ -316,21 +323,10 @@ class OpenStackComputeShell(object): '--os_password', help=argparse.SUPPRESS) - parser.add_argument( - '--os-tenant-name', - metavar='', - default=cliutils.env('OS_TENANT_NAME', 'NOVA_PROJECT_ID'), - help=_('Defaults to env[OS_TENANT_NAME].')) parser.add_argument( '--os_tenant_name', help=argparse.SUPPRESS) - parser.add_argument( - '--os-tenant-id', - metavar='', - default=cliutils.env('OS_TENANT_ID'), - help=_('Defaults to env[OS_TENANT_ID].')) - parser.add_argument( '--os_auth_url', help=argparse.SUPPRESS) @@ -348,7 +344,7 @@ class OpenStackComputeShell(object): '--os-auth-system', metavar='', default=cliutils.env('OS_AUTH_SYSTEM'), - help='Defaults to env[OS_AUTH_SYSTEM].') + help=argparse.SUPPRESS) parser.add_argument( '--os_auth_system', help=argparse.SUPPRESS) @@ -426,12 +422,12 @@ class OpenStackComputeShell(object): # The auth-system-plugins might require some extra options novaclient.auth_plugin.load_auth_system_opts(parser) - self._append_global_identity_args(parser) + self._append_global_identity_args(parser, argv) return parser - def get_subcommand_parser(self, version, do_help=False): - parser = self.get_base_parser() + def get_subcommand_parser(self, version, do_help=False, argv=None): + parser = self.get_base_parser(argv) self.subcommands = {} subparsers = parser.add_subparsers(metavar='') @@ -529,23 +525,9 @@ class OpenStackComputeShell(object): format=streamformat) logging.getLogger('iso8601').setLevel(logging.WARNING) - def _get_keystone_auth(self, session, auth_url, **kwargs): - auth_token = kwargs.pop('auth_token', None) - if auth_token: - return token.Token(auth_url, auth_token, **kwargs) - else: - return password.Password( - auth_url, - username=kwargs.pop('username'), - user_id=kwargs.pop('user_id'), - password=kwargs.pop('password'), - user_domain_id=kwargs.pop('user_domain_id'), - user_domain_name=kwargs.pop('user_domain_name'), - **kwargs) - def main(self, argv): # Parse args once to find version and debug settings - parser = self.get_base_parser() + parser = self.get_base_parser(argv) # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse # thinking usage-list --end is ambiguous; but it @@ -554,6 +536,10 @@ class OpenStackComputeShell(object): if '--endpoint_type' in argv: spot = argv.index('--endpoint_type') argv[spot] = '--endpoint-type' + # For backwards compat with old os-auth-token parameter + if '--os-auth-token' in argv: + spot = argv.index('--os-auth-token') + argv[spot] = '--os-token' (args, args_list) = parser.parse_known_args(argv) @@ -579,8 +565,10 @@ class OpenStackComputeShell(object): os_username = args.os_username os_user_id = args.os_user_id os_password = None # Fetched and set later as needed - os_tenant_name = args.os_tenant_name - os_tenant_id = args.os_tenant_id + os_project_name = getattr( + args, 'os_project_name', getattr(args, 'os_tenant_name', None)) + os_project_id = getattr( + args, 'os_project_id', getattr(args, 'os_tenant_id', None)) os_auth_url = args.os_auth_url os_region_name = args.os_region_name os_auth_system = args.os_auth_system @@ -603,10 +591,14 @@ class OpenStackComputeShell(object): # Finally, authenticate unless we have both. # Note if we don't auth we probably don't have a tenant ID so we can't # cache the token. - auth_token = args.os_auth_token if args.os_auth_token else None + auth_token = getattr(args, 'os_token', None) management_url = bypass_url if bypass_url else None if os_auth_system and os_auth_system != "keystone": + warnings.warn(_( + 'novaclient auth plugins that are not keystone are deprecated.' + ' Auth plugins should now be done as plugins to keystoneauth' + ' and selected with --os-auth-type or OS_AUTH_TYPE')) auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_system) else: auth_plugin = None @@ -648,8 +640,7 @@ class OpenStackComputeShell(object): "or user id via --os-username, --os-user-id, " "env[OS_USERNAME] or env[OS_USER_ID]")) - if not any([args.os_tenant_name, args.os_tenant_id, - args.os_project_id, args.os_project_name]): + if not any([os_project_name, os_project_id]): raise exc.CommandError(_("You must provide a project name or" " project id via --os-project-name," " --os-project-id, env[OS_PROJECT_ID]" @@ -669,34 +660,20 @@ class OpenStackComputeShell(object): "default url with --os-auth-system " "or env[OS_AUTH_SYSTEM]")) - project_id = args.os_project_id or args.os_tenant_id - project_name = args.os_project_name or args.os_tenant_name if use_session: # Not using Nova auth plugin, so use keystone with utils.record_time(self.times, args.timings, 'auth_url', args.os_auth_url): - keystone_session = (ksession.Session - .load_from_cli_options(args)) - keystone_auth = self._get_keystone_auth( - keystone_session, - args.os_auth_url, - username=args.os_username, - user_id=args.os_user_id, - user_domain_id=args.os_user_domain_id, - user_domain_name=args.os_user_domain_name, - password=args.os_password, - auth_token=args.os_auth_token, - project_id=project_id, - project_name=project_name, - project_domain_id=args.os_project_domain_id, - project_domain_name=args.os_project_domain_name) + keystone_session = ( + loading.load_session_from_argparse_arguments(args)) + keystone_auth = ( + loading.load_auth_from_argparse_arguments(args)) else: # set password for auth plugins os_password = args.os_password if (not skip_auth and - not any([args.os_tenant_id, args.os_tenant_name, - args.os_project_id, args.os_project_name])): + not any([os_project_name, os_project_id])): raise exc.CommandError(_("You must provide a project name or" " project id via --os-project-name," " --os-project-id, env[OS_PROJECT_ID]" @@ -713,8 +690,8 @@ class OpenStackComputeShell(object): # microversion, so we just pass version 2 at here. self.cs = client.Client( api_versions.APIVersion("2.0"), - os_username, os_password, os_tenant_name, - tenant_id=os_tenant_id, user_id=os_user_id, + os_username, os_password, os_project_name, + tenant_id=os_project_id, user_id=os_user_id, auth_url=os_auth_url, insecure=insecure, region_name=os_region_name, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, @@ -745,7 +722,7 @@ class OpenStackComputeShell(object): self._run_extension_hooks('__pre_parse_args__') subcommand_parser = self.get_subcommand_parser( - api_version, do_help=do_help) + api_version, do_help=do_help, argv=argv) self.parser = subcommand_parser if args.help or not argv: @@ -777,8 +754,8 @@ class OpenStackComputeShell(object): # Recreate client object with discovered version. self.cs = client.Client( api_version, - os_username, os_password, os_tenant_name, - tenant_id=os_tenant_id, user_id=os_user_id, + os_username, os_password, os_project_name, + tenant_id=os_project_id, user_id=os_user_id, auth_url=os_auth_url, insecure=insecure, region_name=os_region_name, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, diff --git a/novaclient/tests/unit/fixture_data/client.py b/novaclient/tests/unit/fixture_data/client.py index 5f933b750..8aff839b8 100644 --- a/novaclient/tests/unit/fixture_data/client.py +++ b/novaclient/tests/unit/fixture_data/client.py @@ -11,9 +11,9 @@ # under the License. import fixtures -from keystoneclient.auth.identity import v2 -from keystoneclient import fixture -from keystoneclient import session +from keystoneauth1 import fixture +from keystoneauth1 import loading +from keystoneauth1 import session from novaclient.v2 import client as v2client @@ -33,6 +33,7 @@ class V1(fixtures.Fixture): self.token = fixture.V2Token() self.token.set_scope() + self.discovery = fixture.V2Discovery(href=self.identity_url) s = self.token.add_service('compute') s.add_endpoint(self.compute_url) @@ -48,6 +49,9 @@ class V1(fixtures.Fixture): self.requests.register_uri('POST', auth_url, json=self.token, headers=headers) + self.requests.register_uri('GET', self.identity_url, + json=self.discovery, + headers=headers) self.client = self.new_client() def new_client(self): @@ -61,5 +65,7 @@ class SessionV1(V1): def new_client(self): self.session = session.Session() - self.session.auth = v2.Password(self.identity_url, 'xx', 'xx') + loader = loading.get_plugin_loader('password') + self.session.auth = loader.load_from_options( + auth_url=self.identity_url, username='xx', password='xx') return v2client.Client(session=self.session) diff --git a/novaclient/tests/unit/test_auth_plugins.py b/novaclient/tests/unit/test_auth_plugins.py index 9dc02b02a..2257b2a88 100644 --- a/novaclient/tests/unit/test_auth_plugins.py +++ b/novaclient/tests/unit/test_auth_plugins.py @@ -15,7 +15,7 @@ import argparse -from keystoneclient import fixture +from keystoneauth1 import fixture import mock import pkg_resources import requests diff --git a/novaclient/tests/unit/test_client.py b/novaclient/tests/unit/test_client.py index 5038e1837..171ec9d4c 100644 --- a/novaclient/tests/unit/test_client.py +++ b/novaclient/tests/unit/test_client.py @@ -18,7 +18,7 @@ import json import logging import fixtures -from keystoneclient import adapter +from keystoneauth1 import adapter import mock import requests @@ -30,7 +30,7 @@ import novaclient.v2.client class ClientConnectionPoolTest(utils.TestCase): - @mock.patch("keystoneclient.session.TCPKeepAliveAdapter") + @mock.patch("keystoneauth1.session.TCPKeepAliveAdapter") def test_get(self, mock_http_adapter): mock_http_adapter.side_effect = lambda: mock.Mock() pool = novaclient.client._ClientConnectionPool() diff --git a/novaclient/tests/unit/test_service_catalog.py b/novaclient/tests/unit/test_service_catalog.py index 33be77791..ff19dcd5b 100644 --- a/novaclient/tests/unit/test_service_catalog.py +++ b/novaclient/tests/unit/test_service_catalog.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -from keystoneclient import fixture +from keystoneauth1 import fixture from novaclient import exceptions from novaclient import service_catalog diff --git a/novaclient/tests/unit/test_shell.py b/novaclient/tests/unit/test_shell.py index a32c14257..c3003877d 100644 --- a/novaclient/tests/unit/test_shell.py +++ b/novaclient/tests/unit/test_shell.py @@ -16,7 +16,7 @@ import re import sys import fixtures -from keystoneclient import fixture +from keystoneauth1 import fixture import mock import prettytable import requests_mock diff --git a/novaclient/tests/unit/v2/test_auth.py b/novaclient/tests/unit/v2/test_auth.py index 696428a4a..a62866a62 100644 --- a/novaclient/tests/unit/v2/test_auth.py +++ b/novaclient/tests/unit/v2/test_auth.py @@ -14,7 +14,7 @@ import copy import json -from keystoneclient import fixture +from keystoneauth1 import fixture import mock import requests diff --git a/novaclient/tests/unit/v2/test_client.py b/novaclient/tests/unit/v2/test_client.py index b4c59ce38..5bbbe51fb 100644 --- a/novaclient/tests/unit/v2/test_client.py +++ b/novaclient/tests/unit/v2/test_client.py @@ -12,7 +12,7 @@ import uuid -from keystoneclient import session +from keystoneauth1 import session from novaclient.tests.unit import utils from novaclient.v2 import client diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 05a0e74d4..8f0727d48 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -27,6 +27,7 @@ import logging import os import sys import time +import warnings from oslo_utils import encodeutils from oslo_utils import strutils @@ -3874,10 +3875,11 @@ def ensure_service_catalog_present(cs): def do_endpoints(cs, _args): """Discover endpoints that get returned from the authenticate services.""" + warnings.warn( + "nova endpoints is deprecated, use openstack catalog list instead") if isinstance(cs.client, client.SessionClient): - auth = cs.client.auth - sc = auth.get_access(cs.client.session).service_catalog - for service in sc.get_data(): + access = cs.client.auth.get_access(cs.client.session) + for service in access.service_catalog.catalog: _print_endpoints(service, cs.client.region_name) else: ensure_service_catalog_present(cs) @@ -3926,12 +3928,14 @@ def _get_first_endpoint(endpoints, region): help=_('Wrap PKI tokens to a specified length, or 0 to disable.')) def do_credentials(cs, _args): """Show user credentials returned from auth.""" + warnings.warn( + "nova credentials is deprecated, use openstack client instead") if isinstance(cs.client, client.SessionClient): - auth = cs.client.auth - sc = auth.get_access(cs.client.session).service_catalog - utils.print_dict(sc.catalog['user'], 'User Credentials', + access = cs.client.auth.get_access(cs.client.session) + utils.print_dict(access._user, 'User Credentials', wrap=int(_args.wrap)) - utils.print_dict(sc.get_token(), 'Token', wrap=int(_args.wrap)) + if hasattr(access, '_token'): + utils.print_dict(access._token, 'Token', wrap=int(_args.wrap)) else: ensure_service_catalog_present(cs) catalog = cs.client.service_catalog.catalog diff --git a/releasenotes/notes/keystoneauth-8ec1e6be14cdbae3.yaml b/releasenotes/notes/keystoneauth-8ec1e6be14cdbae3.yaml new file mode 100644 index 000000000..d9e2d7ffa --- /dev/null +++ b/releasenotes/notes/keystoneauth-8ec1e6be14cdbae3.yaml @@ -0,0 +1,11 @@ +--- +features: +- keystoneauth plugins are now supported. +upgrade: +- novaclient now requires the keystoneauth library. +deprecations: +- novaclient auth strategy plugins are deprecated. Please use + keystoneauth auth plugins instead. +- nova credentials is deprecated. Please use openstack token issue +- nova endpoints is deprecated. Please use openstack catalog list + instead. diff --git a/requirements.txt b/requirements.txt index 32826add3..84d5f79f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ # process, which may cause wedges in the gate later. pbr>=1.6 argparse +keystoneauth1>=2.1.0 iso8601>=0.1.9 oslo.i18n>=1.5.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 @@ -12,4 +13,3 @@ requests>=2.8.1 simplejson>=2.2.0 six>=1.9.0 Babel>=1.3 -python-keystoneclient!=1.8.0,>=1.6.0 diff --git a/test-requirements.txt b/test-requirements.txt index 0ab103545..8ffcf36cd 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,6 +8,7 @@ discover fixtures>=1.3.1 keyring>=5.5.1 mock>=1.2 +python-keystoneclient!=1.8.0,>=1.6.0 requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-client-config!=1.6.2,>=1.4.0