summaryrefslogtreecommitdiff
path: root/novaclient
diff options
context:
space:
mode:
Diffstat (limited to 'novaclient')
-rw-r--r--novaclient/auth_plugin.py141
-rw-r--r--novaclient/client.py29
-rw-r--r--novaclient/shell.py40
-rw-r--r--novaclient/utils.py10
-rw-r--r--novaclient/v1_1/client.py2
5 files changed, 195 insertions, 27 deletions
diff --git a/novaclient/auth_plugin.py b/novaclient/auth_plugin.py
new file mode 100644
index 00000000..39da86aa
--- /dev/null
+++ b/novaclient/auth_plugin.py
@@ -0,0 +1,141 @@
+# 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
+
+from novaclient import exceptions
+from novaclient 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 _discovered_plugins.iteritems():
+ 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/novaclient/client.py b/novaclient/client.py
index 73595701..e73a6dd4 100644
--- a/novaclient/client.py
+++ b/novaclient/client.py
@@ -31,15 +31,6 @@ from novaclient import service_catalog
from novaclient import utils
-def get_auth_system_url(auth_system):
- """Load plugin-based auth_url"""
- ep_name = 'openstack.client.auth_url'
- for ep in pkg_resources.iter_entry_points(ep_name):
- if ep.name == auth_system:
- return ep.load()()
- raise exceptions.AuthSystemNotFound(auth_system)
-
-
class HTTPClient(object):
USER_AGENT = 'python-novaclient'
@@ -52,12 +43,17 @@ class HTTPClient(object):
timings=False, bypass_url=None,
os_cache=False, no_cache=True,
http_log_debug=False, auth_system='keystone',
+ auth_plugin=None,
cacert=None):
self.user = user
self.password = password
self.projectid = projectid
+
+ 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 = get_auth_system_url(auth_system)
+ auth_url = auth_plugin.get_auth_url()
if not auth_url:
raise exceptions.EndpointNotFound()
self.auth_url = auth_url.rstrip('/')
@@ -94,6 +90,7 @@ class HTTPClient(object):
self.verify_cert = True
self.auth_system = auth_system
+ self.auth_plugin = auth_plugin
self._logger = logging.getLogger(__name__)
if self.http_log_debug:
@@ -392,12 +389,7 @@ class HTTPClient(object):
raise exceptions.from_response(resp, body, url)
def _plugin_auth(self, auth_url):
- """Load plugin-based authentication"""
- ep_name = 'openstack.client.authenticate'
- for ep in pkg_resources.iter_entry_points(ep_name):
- if ep.name == self.auth_system:
- return ep.load()(self, auth_url)
- raise exceptions.AuthSystemNotFound(self.auth_system)
+ self.auth_plugin.authenticate(self, auth_url)
def _v2_auth(self, url):
"""Authenticate against a v2.0 auth service."""
@@ -414,7 +406,7 @@ class HTTPClient(object):
self._authenticate(url, body)
- def _authenticate(self, url, body):
+ def _authenticate(self, url, body, **kwargs):
"""Authenticate and extract the service catalog."""
token_url = url + "/tokens"
@@ -423,7 +415,8 @@ class HTTPClient(object):
token_url,
"POST",
body=body,
- allow_redirects=True)
+ allow_redirects=True,
+ **kwargs)
return self._extract_service_catalog(url, resp, body)
diff --git a/novaclient/shell.py b/novaclient/shell.py
index dd7ce8a5..b07ee380 100644
--- a/novaclient/shell.py
+++ b/novaclient/shell.py
@@ -46,6 +46,7 @@ except ImportError:
pass
import novaclient
+import novaclient.auth_plugin
from novaclient import client
from novaclient import exceptions as exc
import novaclient.extension
@@ -398,6 +399,9 @@ class OpenStackComputeShell(object):
parser.add_argument('--bypass_url',
help=argparse.SUPPRESS)
+ # The auth-system-plugins might require some extra options
+ novaclient.auth_plugin.load_auth_system_opts(parser)
+
return parser
def get_subcommand_parser(self, version):
@@ -514,11 +518,15 @@ class OpenStackComputeShell(object):
format=streamformat)
def main(self, argv):
+
# Parse args once to find version and debug settings
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
self.setup_debugging(options.debug)
+ # Discover available auth plugins
+ novaclient.auth_plugin.discover_auth_systems()
+
# build available subcommands based on version
self.extensions = self._discover_extensions(
options.os_compute_api_version)
@@ -566,6 +574,11 @@ class OpenStackComputeShell(object):
args.bypass_url, args.os_cache,
args.os_cacert, args.timeout)
+ if os_auth_system and os_auth_system != "keystone":
+ auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_system)
+ else:
+ auth_plugin = None
+
# Fetched and set later as needed
os_password = None
@@ -579,12 +592,16 @@ class OpenStackComputeShell(object):
#FIXME(usrleon): Here should be restrict for project id same as
# 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_tenant_name:
if not projectid:
@@ -597,8 +614,7 @@ class OpenStackComputeShell(object):
if not os_auth_url:
if not url:
if os_auth_system and os_auth_system != 'keystone':
- os_auth_url = \
- client.get_auth_system_url(os_auth_system)
+ os_auth_url = auth_plugin.get_auth_url()
else:
os_auth_url = url
@@ -627,6 +643,7 @@ class OpenStackComputeShell(object):
region_name=os_region_name, endpoint_type=endpoint_type,
extensions=self.extensions, service_type=service_type,
service_name=service_name, auth_system=os_auth_system,
+ auth_plugin=auth_plugin,
volume_service_name=volume_service_name,
timings=args.timings, bypass_url=bypass_url,
os_cache=os_cache, http_log_debug=options.debug,
@@ -636,7 +653,12 @@ class OpenStackComputeShell(object):
# identifying keyring key can come from the underlying client
if not utils.isunauthenticated(args.func):
helper = SecretsHelper(args, self.cs.client)
- use_pw = True
+ if (auth_plugin and auth_plugin.opts and
+ "os_password" not in auth_plugin.opts):
+ use_pw = False
+ else:
+ use_pw = True
+
tenant_id, auth_token, management_url = (helper.tenant_id,
helper.auth_token,
helper.management_url)
diff --git a/novaclient/utils.py b/novaclient/utils.py
index 67a5e53b..280bef0d 100644
--- a/novaclient/utils.py
+++ b/novaclient/utils.py
@@ -1,4 +1,5 @@
import os
+import pkg_resources
import re
import sys
import textwrap
@@ -369,3 +370,12 @@ def check_uuid_like(val):
raise exceptions.CommandError(
"error: Invalid tenant-id %s supplied"
% val)
+
+
+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
diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py
index cae702d1..7b93cc36 100644
--- a/novaclient/v1_1/client.py
+++ b/novaclient/v1_1/client.py
@@ -74,6 +74,7 @@ class Client(object):
volume_service_name=None, timings=False,
bypass_url=None, os_cache=False, no_cache=True,
http_log_debug=False, auth_system='keystone',
+ auth_plugin=None,
cacert=None):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
@@ -132,6 +133,7 @@ class Client(object):
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
+ auth_plugin=auth_plugin,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,