From d5334aa929beb4190ae04fddc98e693df142b9bd Mon Sep 17 00:00:00 2001 From: Cory Stone Date: Fri, 14 Feb 2014 13:42:58 -0600 Subject: [PATCH] Add auth_plugin support to cinderclient With CINDER_RAX_AUTH being rightfully removed, cinderclient is no longer compatible with Rackspace/any non-keystone auth. To fix this, I stole auth_system/auth_plugin from novaclient's implementation. See https://review.openstack.org/#/c/23820/. Change-Id: If5f84003f868ef02bb7eb7da67cf62018602e8f0 Closes-Bug: 1280393 --- cinderclient/auth_plugin.py | 143 ++++++++++ cinderclient/client.py | 29 +- cinderclient/exceptions.py | 9 + cinderclient/shell.py | 48 +++- cinderclient/tests/test_auth_plugins.py | 346 ++++++++++++++++++++++++ cinderclient/utils.py | 10 + cinderclient/v1/client.py | 8 +- cinderclient/v2/client.py | 8 +- tools/install_venv_common.py | 3 + 9 files changed, 583 insertions(+), 21 deletions(-) create mode 100644 cinderclient/auth_plugin.py create mode 100644 cinderclient/tests/test_auth_plugins.py diff --git a/cinderclient/auth_plugin.py b/cinderclient/auth_plugin.py new file mode 100644 index 0000000..2101b93 --- /dev/null +++ b/cinderclient/auth_plugin.py @@ -0,0 +1,143 @@ +# Copyright 2013 OpenStack Foundation +# Copyright 2013 Spanish National Research Council. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import pkg_resources + +import six + +from cinderclient import exceptions +from cinderclient import utils + + +logger = logging.getLogger(__name__) + + +_discovered_plugins = {} + + +def discover_auth_systems(): + """Discover the available auth-systems. + + This won't take into account the old style auth-systems. + """ + ep_name = 'openstack.client.auth_plugin' + for ep in pkg_resources.iter_entry_points(ep_name): + try: + auth_plugin = ep.load() + except (ImportError, pkg_resources.UnknownExtra, AttributeError) as e: + logger.debug("ERROR: Cannot load auth plugin %s" % ep.name) + logger.debug(e, exc_info=1) + else: + _discovered_plugins[ep.name] = auth_plugin + + +def load_auth_system_opts(parser): + """Load options needed by the available auth-systems into a parser. + + This function will try to populate the parser with options from the + available plugins. + """ + for name, auth_plugin in six.iteritems(_discovered_plugins): + add_opts_fn = getattr(auth_plugin, "add_opts", None) + if add_opts_fn: + group = parser.add_argument_group("Auth-system '%s' options" % + name) + add_opts_fn(group) + + +def load_plugin(auth_system): + if auth_system in _discovered_plugins: + return _discovered_plugins[auth_system]() + + # NOTE(aloga): If we arrive here, the plugin will be an old-style one, + # so we have to create a fake AuthPlugin for it. + return DeprecatedAuthPlugin(auth_system) + + +class BaseAuthPlugin(object): + """Base class for authentication plugins. + + An authentication plugin needs to override at least the authenticate + method to be a valid plugin. + """ + def __init__(self): + self.opts = {} + + def get_auth_url(self): + """Return the auth url for the plugin (if any).""" + return None + + @staticmethod + def add_opts(parser): + """Populate and return the parser with the options for this plugin. + + If the plugin does not need any options, it should return the same + parser untouched. + """ + return parser + + def parse_opts(self, args): + """Parse the actual auth-system options if any. + + This method is expected to populate the attribute self.opts with a + dict containing the options and values needed to make authentication. + If the dict is empty, the client should assume that it needs the same + options as the 'keystone' auth system (i.e. os_username and + os_password). + + Returns the self.opts dict. + """ + return self.opts + + def authenticate(self, cls, auth_url): + """Authenticate using plugin defined method.""" + raise exceptions.AuthSystemNotFound(self.auth_system) + + +class DeprecatedAuthPlugin(object): + """Class to mimic the AuthPlugin class for deprecated auth systems. + + Old auth systems only define two entry points: openstack.client.auth_url + and openstack.client.authenticate. This class will load those entry points + into a class similar to a valid AuthPlugin. + """ + def __init__(self, auth_system): + self.auth_system = auth_system + + def authenticate(cls, auth_url): + raise exceptions.AuthSystemNotFound(self.auth_system) + + self.opts = {} + + self.get_auth_url = lambda: None + self.authenticate = authenticate + + self._load_endpoints() + + def _load_endpoints(self): + ep_name = 'openstack.client.auth_url' + fn = utils._load_entry_point(ep_name, name=self.auth_system) + if fn: + self.get_auth_url = fn + + ep_name = 'openstack.client.authenticate' + fn = utils._load_entry_point(ep_name, name=self.auth_system) + if fn: + self.authenticate = fn + + def parse_opts(self, args): + return self.opts diff --git a/cinderclient/client.py b/cinderclient/client.py index c268137..3354b7b 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -54,16 +54,26 @@ class HTTPClient(object): USER_AGENT = 'python-cinderclient' - def __init__(self, user, password, projectid, auth_url, insecure=False, - timeout=None, tenant_id=None, proxy_tenant_id=None, - proxy_token=None, region_name=None, + def __init__(self, user, password, projectid, auth_url=None, + insecure=False, timeout=None, tenant_id=None, + proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', service_type=None, service_name=None, volume_service_name=None, retries=None, - http_log_debug=False, cacert=None): + http_log_debug=False, cacert=None, + auth_system='keystone', auth_plugin=None): self.user = user self.password = password self.projectid = projectid self.tenant_id = tenant_id + + if auth_system and auth_system != 'keystone' and not auth_plugin: + raise exceptions.AuthSystemNotFound(auth_system) + + if not auth_url and auth_system and auth_system != 'keystone': + auth_url = auth_plugin.get_auth_url() + if not auth_url: + raise exceptions.EndpointNotFound() + self.auth_url = auth_url.rstrip('/') self.version = 'v1' self.region_name = region_name @@ -88,6 +98,9 @@ class HTTPClient(object): else: self.verify_cert = True + self.auth_system = auth_system + self.auth_plugin = auth_plugin + self._logger = logging.getLogger(__name__) if self.http_log_debug and not self._logger.handlers: ch = logging.StreamHandler() @@ -295,7 +308,10 @@ class HTTPClient(object): auth_url = self.auth_url if self.version == "v2.0": while auth_url: - auth_url = self._v2_auth(auth_url) + if not self.auth_system or self.auth_system == 'keystone': + auth_url = self._v2_auth(auth_url) + else: + auth_url = self._plugin_auth(auth_url) # Are we acting on behalf of another user via an # existing token? If so, our actual endpoints may @@ -341,6 +357,9 @@ class HTTPClient(object): else: raise exceptions.from_response(resp, body) + def _plugin_auth(self, auth_url): + return self.auth_plugin.authenticate(self, auth_url) + def _v2_auth(self, url): """Authenticate against a v2.0 auth service.""" body = {"auth": { diff --git a/cinderclient/exceptions.py b/cinderclient/exceptions.py index 1e3050c..41796e3 100644 --- a/cinderclient/exceptions.py +++ b/cinderclient/exceptions.py @@ -41,6 +41,15 @@ class NoUniqueMatch(Exception): pass +class AuthSystemNotFound(Exception): + """When the user specify a AuthSystem but not installed.""" + def __init__(self, auth_system): + self.auth_system = auth_system + + def __str__(self): + return "AuthSystemNotFound: %s" % repr(self.auth_system) + + class NoTokenLookupException(Exception): """This form of authentication does not support looking up endpoints from an existing token. diff --git a/cinderclient/shell.py b/cinderclient/shell.py index 385b05d..17e7d77 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -29,6 +29,7 @@ import pkgutil import sys import logging +import cinderclient.auth_plugin from cinderclient import client from cinderclient import exceptions as exc import cinderclient.extension @@ -142,6 +143,13 @@ class OpenStackCinderShell(object): parser.add_argument('--os_region_name', help=argparse.SUPPRESS) + parser.add_argument('--os-auth-system', + metavar='', + default=utils.env('OS_AUTH_SYSTEM'), + help='Defaults to env[OS_AUTH_SYSTEM].') + parser.add_argument('--os_auth_system', + help=argparse.SUPPRESS) + parser.add_argument('--service-type', metavar='', help='Defaults to volume for most actions') @@ -225,6 +233,10 @@ class OpenStackCinderShell(object): default=utils.env('CINDER_URL'), help=argparse.SUPPRESS) + # The auth-system-plugins might require some extra options + cinderclient.auth_plugin.discover_auth_systems() + cinderclient.auth_plugin.load_auth_system_opts(parser) + return parser def get_subcommand_parser(self, version): @@ -372,7 +384,8 @@ class OpenStackCinderShell(object): (os_username, os_password, os_tenant_name, os_auth_url, os_region_name, os_tenant_id, endpoint_type, insecure, service_type, service_name, volume_service_name, - username, apikey, projectid, url, region_name, cacert) = ( + username, apikey, projectid, url, region_name, cacert, + os_auth_system) = ( args.os_username, args.os_password, args.os_tenant_name, args.os_auth_url, args.os_region_name, args.os_tenant_id, @@ -380,7 +393,13 @@ class OpenStackCinderShell(object): args.service_type, args.service_name, args.volume_service_name, args.username, args.apikey, args.projectid, - args.url, args.region_name, args.os_cacert) + args.url, args.region_name, args.os_cacert, + args.os_auth_system) + + if os_auth_system and os_auth_system != "keystone": + auth_plugin = cinderclient.auth_plugin.load_plugin(os_auth_system) + else: + auth_plugin = None if not endpoint_type: endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE @@ -393,13 +412,17 @@ class OpenStackCinderShell(object): # for os_username or os_password but for compatibility it is not. if not utils.isunauthenticated(args.func): - if not os_username: - if not username: - raise exc.CommandError( - "You must provide a username " - "via either --os-username or env[OS_USERNAME]") - else: - os_username = username + if auth_plugin: + auth_plugin.parse_opts(args) + + if not auth_plugin or not auth_plugin.opts: + if not os_username: + if not username: + raise exc.CommandError( + "You must provide a username " + "via either --os-username or env[OS_USERNAME]") + else: + os_username = username if not os_password: if not apikey: @@ -417,6 +440,10 @@ class OpenStackCinderShell(object): else: os_tenant_name = projectid + if not os_auth_url: + if os_auth_system and os_auth_system != 'keystone': + os_auth_url = auth_plugin.get_auth_url() + if not os_auth_url: if not url: raise exc.CommandError( @@ -449,7 +476,8 @@ class OpenStackCinderShell(object): volume_service_name=volume_service_name, retries=options.retries, http_log_debug=args.debug, - cacert=cacert) + cacert=cacert, auth_system=os_auth_system, + auth_plugin=auth_plugin) try: if not utils.isunauthenticated(args.func): diff --git a/cinderclient/tests/test_auth_plugins.py b/cinderclient/tests/test_auth_plugins.py new file mode 100644 index 0000000..9a95114 --- /dev/null +++ b/cinderclient/tests/test_auth_plugins.py @@ -0,0 +1,346 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse +import mock +import pkg_resources +import requests + +try: + import json +except ImportError: + import simplejson as json + +from cinderclient import auth_plugin +from cinderclient import exceptions +from cinderclient.tests import utils +from cinderclient.v1 import client + + +def mock_http_request(resp=None): + """Mock an HTTP Request.""" + if not resp: + resp = { + "access": { + "token": { + "expires": "12345", + "id": "FAKE_ID", + "tenant": { + "id": "FAKE_TENANT_ID", + } + }, + "serviceCatalog": [ + { + "type": "volume", + "endpoints": [ + { + "region": "RegionOne", + "adminURL": "http://localhost:8774/v1.1", + "internalURL": "http://localhost:8774/v1.1", + "publicURL": "http://localhost:8774/v1.1/", + }, + ], + }, + ], + }, + } + + auth_response = utils.TestResponse({ + "status_code": 200, + "text": json.dumps(resp), + }) + return mock.Mock(return_value=(auth_response)) + + +def requested_headers(cs): + """Return requested passed headers.""" + return { + 'User-Agent': cs.client.USER_AGENT, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + + +class DeprecatedAuthPluginTest(utils.TestCase): + def test_auth_system_success(self): + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return self.authenticate + + def authenticate(self, cls, auth_url): + cls._authenticate(auth_url, {"fake": "me"}) + + def mock_iter_entry_points(_type, name): + if _type == 'openstack.client.authenticate': + return [MockEntrypoint("fake", "fake", ["fake"])] + else: + return [] + + mock_request = mock_http_request() + + @mock.patch.object(pkg_resources, "iter_entry_points", + mock_iter_entry_points) + @mock.patch.object(requests, "request", mock_request) + def test_auth_call(): + plugin = auth_plugin.DeprecatedAuthPlugin("fake") + cs = client.Client("username", "password", "project_id", + "auth_url/v2.0", auth_system="fake", + auth_plugin=plugin) + cs.client.authenticate() + + headers = requested_headers(cs) + token_url = cs.client.auth_url + "/tokens" + + mock_request.assert_called_with( + "POST", + token_url, + headers=headers, + data='{"fake": "me"}', + allow_redirects=True, + **self.TEST_REQUEST_BASE) + + test_auth_call() + + def test_auth_system_not_exists(self): + def mock_iter_entry_points(_t, name=None): + return [pkg_resources.EntryPoint("fake", "fake", ["fake"])] + + mock_request = mock_http_request() + + @mock.patch.object(pkg_resources, "iter_entry_points", + mock_iter_entry_points) + @mock.patch.object(requests.Session, "request", mock_request) + def test_auth_call(): + auth_plugin.discover_auth_systems() + plugin = auth_plugin.DeprecatedAuthPlugin("notexists") + cs = client.Client("username", "password", "project_id", + "auth_url/v2.0", auth_system="notexists", + auth_plugin=plugin) + self.assertRaises(exceptions.AuthSystemNotFound, + cs.client.authenticate) + + test_auth_call() + + def test_auth_system_defining_auth_url(self): + class MockAuthUrlEntrypoint(pkg_resources.EntryPoint): + def load(self): + return self.auth_url + + def auth_url(self): + return "http://faked/v2.0" + + class MockAuthenticateEntrypoint(pkg_resources.EntryPoint): + def load(self): + return self.authenticate + + def authenticate(self, cls, auth_url): + cls._authenticate(auth_url, {"fake": "me"}) + + def mock_iter_entry_points(_type, name): + if _type == 'openstack.client.auth_url': + return [MockAuthUrlEntrypoint("fakewithauthurl", + "fakewithauthurl", + ["auth_url"])] + elif _type == 'openstack.client.authenticate': + return [MockAuthenticateEntrypoint("fakewithauthurl", + "fakewithauthurl", + ["authenticate"])] + else: + return [] + + mock_request = mock_http_request() + + @mock.patch.object(pkg_resources, "iter_entry_points", + mock_iter_entry_points) + @mock.patch.object(requests.Session, "request", mock_request) + def test_auth_call(): + plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl") + cs = client.Client("username", "password", "project_id", + auth_system="fakewithauthurl", + auth_plugin=plugin) + cs.client.authenticate() + self.assertEqual(cs.client.auth_url, "http://faked/v2.0") + + test_auth_call() + + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_client_raises_exc_without_auth_url(self, mock_iter_entry_points): + class MockAuthUrlEntrypoint(pkg_resources.EntryPoint): + def load(self): + return self.auth_url + + def auth_url(self): + return None + + mock_iter_entry_points.side_effect = lambda _t, name: [ + MockAuthUrlEntrypoint("fakewithauthurl", + "fakewithauthurl", + ["auth_url"])] + + plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl") + self.assertRaises( + exceptions.EndpointNotFound, + client.Client, "username", "password", "project_id", + auth_system="fakewithauthurl", auth_plugin=plugin) + + +class AuthPluginTest(utils.TestCase): + @mock.patch.object(requests, "request") + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_auth_system_success(self, mock_iter_entry_points, mock_request): + """Test that we can authenticate using the auth system.""" + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return FakePlugin + + class FakePlugin(auth_plugin.BaseAuthPlugin): + def authenticate(self, cls, auth_url): + cls._authenticate(auth_url, {"fake": "me"}) + + mock_iter_entry_points.side_effect = lambda _t: [ + MockEntrypoint("fake", "fake", ["FakePlugin"])] + + mock_request.side_effect = mock_http_request() + + auth_plugin.discover_auth_systems() + plugin = auth_plugin.load_plugin("fake") + cs = client.Client("username", "password", "project_id", + "auth_url/v2.0", auth_system="fake", + auth_plugin=plugin) + cs.client.authenticate() + + headers = requested_headers(cs) + token_url = cs.client.auth_url + "/tokens" + + mock_request.assert_called_with( + "POST", + token_url, + headers=headers, + data='{"fake": "me"}', + allow_redirects=True, + **self.TEST_REQUEST_BASE) + + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_discover_auth_system_options(self, mock_iter_entry_points): + """Test that we can load the auth system options.""" + class FakePlugin(auth_plugin.BaseAuthPlugin): + @staticmethod + def add_opts(parser): + parser.add_argument('--auth_system_opt', + default=False, + action='store_true', + help="Fake option") + return parser + + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return FakePlugin + + mock_iter_entry_points.side_effect = lambda _t: [ + MockEntrypoint("fake", "fake", ["FakePlugin"])] + + parser = argparse.ArgumentParser() + auth_plugin.discover_auth_systems() + auth_plugin.load_auth_system_opts(parser) + opts, args = parser.parse_known_args(['--auth_system_opt']) + + self.assertTrue(opts.auth_system_opt) + + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_parse_auth_system_options(self, mock_iter_entry_points): + """Test that we can parse the auth system options.""" + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return FakePlugin + + class FakePlugin(auth_plugin.BaseAuthPlugin): + def __init__(self): + self.opts = {"fake_argument": True} + + def parse_opts(self, args): + return self.opts + + mock_iter_entry_points.side_effect = lambda _t: [ + MockEntrypoint("fake", "fake", ["FakePlugin"])] + + auth_plugin.discover_auth_systems() + plugin = auth_plugin.load_plugin("fake") + + plugin.parse_opts([]) + self.assertIn("fake_argument", plugin.opts) + + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_auth_system_defining_url(self, mock_iter_entry_points): + """Test the auth_system defining an url.""" + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return FakePlugin + + class FakePlugin(auth_plugin.BaseAuthPlugin): + def get_auth_url(self): + return "http://faked/v2.0" + + mock_iter_entry_points.side_effect = lambda _t: [ + MockEntrypoint("fake", "fake", ["FakePlugin"])] + + auth_plugin.discover_auth_systems() + plugin = auth_plugin.load_plugin("fake") + + cs = client.Client("username", "password", "project_id", + auth_system="fakewithauthurl", + auth_plugin=plugin) + self.assertEqual(cs.client.auth_url, "http://faked/v2.0") + + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_exception_if_no_authenticate(self, mock_iter_entry_points): + """Test that no authenticate raises a proper exception.""" + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return FakePlugin + + class FakePlugin(auth_plugin.BaseAuthPlugin): + pass + + mock_iter_entry_points.side_effect = lambda _t: [ + MockEntrypoint("fake", "fake", ["FakePlugin"])] + + auth_plugin.discover_auth_systems() + plugin = auth_plugin.load_plugin("fake") + + self.assertRaises( + exceptions.EndpointNotFound, + client.Client, "username", "password", "project_id", + auth_system="fake", auth_plugin=plugin) + + @mock.patch.object(pkg_resources, "iter_entry_points") + def test_exception_if_no_url(self, mock_iter_entry_points): + """Test that no auth_url at all raises exception.""" + class MockEntrypoint(pkg_resources.EntryPoint): + def load(self): + return FakePlugin + + class FakePlugin(auth_plugin.BaseAuthPlugin): + pass + + mock_iter_entry_points.side_effect = lambda _t: [ + MockEntrypoint("fake", "fake", ["FakePlugin"])] + + auth_plugin.discover_auth_systems() + plugin = auth_plugin.load_plugin("fake") + + self.assertRaises( + exceptions.EndpointNotFound, + client.Client, "username", "password", "project_id", + auth_system="fake", auth_plugin=plugin) diff --git a/cinderclient/utils.py b/cinderclient/utils.py index a941873..018f12a 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -16,6 +16,7 @@ from __future__ import print_function import os +import pkg_resources import re import sys import uuid @@ -286,6 +287,15 @@ def import_class(import_str): __import__(mod_str) return getattr(sys.modules[mod_str], class_str) + +def _load_entry_point(ep_name, name=None): + """Try to load the entry point ep_name that matches name.""" + for ep in pkg_resources.iter_entry_points(ep_name, name=name): + try: + return ep.load() + except (ImportError, pkg_resources.UnknownExtra, AttributeError): + continue + _slugify_strip_re = re.compile(r'[^\w\s-]') _slugify_hyphenate_re = re.compile(r'[-\s]+') diff --git a/cinderclient/v1/client.py b/cinderclient/v1/client.py index 82d1ee0..c352927 100644 --- a/cinderclient/v1/client.py +++ b/cinderclient/v1/client.py @@ -50,8 +50,8 @@ class Client(object): endpoint_type='publicURL', extensions=None, service_type='volume', service_name=None, volume_service_name=None, retries=None, - http_log_debug=False, - cacert=None): + http_log_debug=False, cacert=None, + auth_system='keystone', auth_plugin=None): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key @@ -97,7 +97,9 @@ class Client(object): volume_service_name=volume_service_name, retries=retries, http_log_debug=http_log_debug, - cacert=cacert) + cacert=cacert, + auth_system=auth_system, + auth_plugin=auth_plugin) def authenticate(self): """ diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py index 7b91c23..aa0526f 100644 --- a/cinderclient/v2/client.py +++ b/cinderclient/v2/client.py @@ -48,8 +48,8 @@ class Client(object): endpoint_type='publicURL', extensions=None, service_type='volumev2', service_name=None, volume_service_name=None, retries=None, - http_log_debug=False, - cacert=None): + http_log_debug=False, cacert=None, + auth_system='keystone', auth_plugin=None): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key @@ -95,7 +95,9 @@ class Client(object): volume_service_name=volume_service_name, retries=retries, http_log_debug=http_log_debug, - cacert=cacert) + cacert=cacert, + auth_system=auth_system, + auth_plugin=auth_plugin) def authenticate(self): """Authenticate against the server. diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py index 46822e3..3b7ac10 100644 --- a/tools/install_venv_common.py +++ b/tools/install_venv_common.py @@ -128,6 +128,9 @@ class InstallVenv(object): "install") return parser.parse_args(argv[1:])[0] + def post_process(self, **kwargs): + pass + class Distro(InstallVenv):