diff options
-rw-r--r-- | README.rst | 42 | ||||
-rw-r--r-- | os_client_config/config.py | 87 | ||||
-rw-r--r-- | os_client_config/vendors.py | 9 |
3 files changed, 104 insertions, 34 deletions
@@ -57,23 +57,27 @@ An example config file is probably helpful: clouds: mordred: cloud: hp - username: mordred@inaugust.com - password: XXXXXXXXX - project_id: mordred@inaugust.com + auth: + username: mordred@inaugust.com + password: XXXXXXXXX + project_name: mordred@inaugust.com region_name: region-b.geo-1 dns_service_type: hpext:dns + compute_api_version: 1.1 monty: - auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0 - username: monty.taylor@hp.com - password: XXXXXXXX - project_id: monty.taylor@hp.com-default-tenant + auth: + auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0 + username: monty.taylor@hp.com + password: XXXXXXXX + project_name: monty.taylor@hp.com-default-tenant region_name: region-b.geo-1 dns_service_type: hpext:dns infra: cloud: rackspace - username: openstackci - password: XXXXXXXX - project_id: 610275 + auth: + username: openstackci + password: XXXXXXXX + project_id: 610275 region_name: DFW,ORD,IAD You may note a few things. First, since auth_url settings are silly @@ -92,6 +96,17 @@ the setting with the default service type. That might strike you funny when setting `service_type` and it does me too - but that's just the world we live in. +Auth Settings +------------- + +Keystone has auth plugins - which means it's not possible to know ahead of time +which auth settings are needed. `os-client-config` sets the default plugin type +to `password`, which is what things all were before plugins came about. In +order to facilitate validation of values, all of the parameters that exist +as a result of a chosen plugin need to go into the auth dict. For password +auth, this includes `auth_url`, `username` and `password` as well as anything +related to domains, projects and trusts. + Cache Settings -------------- @@ -107,9 +122,10 @@ understands a simple set of cache control settings. clouds: mordred: cloud: hp - username: mordred@inaugust.com - password: XXXXXXXXX - project_id: mordred@inaugust.com + auth: + username: mordred@inaugust.com + password: XXXXXXXXX + project_name: mordred@inaugust.com region_name: region-b.geo-1 dns_service_type: hpext:dns diff --git a/os_client_config/config.py b/os_client_config/config.py index 9c3ca87..67436fa 100644 --- a/os_client_config/config.py +++ b/os_client_config/config.py @@ -17,6 +17,11 @@ import os import yaml +try: + import keystoneclient.auth as ksc_auth +except ImportError: + ksc_auth = None + from os_client_config import cloud_config from os_client_config import exceptions from os_client_config import vendors @@ -31,7 +36,6 @@ CACHE_PATH = os.path.join(os.path.expanduser( os.environ.get('XDG_CACHE_PATH', os.path.join('~', '.cache'))), 'openstack') BOOL_KEYS = ('insecure', 'cache') -REQUIRED_VALUES = ('auth_url', 'username', 'password') VENDOR_SEARCH_PATH = [os.getcwd(), CONFIG_HOME, '/etc/openstack'] VENDOR_FILES = [ os.path.join(d, 'clouds-public.yaml') for d in VENDOR_SEARCH_PATH] @@ -44,7 +48,7 @@ def get_boolean(value): def _get_os_environ(): - ret = dict() + ret = dict(auth_plugin='password', auth=dict()) for (k, v) in os.environ.items(): if k.startswith('OS_'): newkey = k[3:].lower() @@ -52,6 +56,16 @@ def _get_os_environ(): return ret +def _auth_update(old_dict, new_dict): + """Like dict.update, except handling the nested dict called auth.""" + for (k, v) in new_dict.items(): + if k == 'auth': + old_dict[k].update(v) + else: + old_dict[k] = v + return old_dict + + class OpenStackConfig(object): def __init__(self, config_files=None, vendor_files=None): @@ -124,15 +138,15 @@ class OpenStackConfig(object): cloud_name = our_cloud['cloud'] vendor_file = self._load_vendor_file() if vendor_file and cloud_name in vendor_file['public-clouds']: - cloud.update(vendor_file['public-clouds'][cloud_name]) + _auth_update(cloud, vendor_file['public-clouds'][cloud_name]) else: try: - cloud.update(vendors.CLOUD_DEFAULTS[cloud_name]) + _auth_update(cloud, vendors.CLOUD_DEFAULTS[cloud_name]) except KeyError: # Can't find the requested vendor config, go about business pass - cloud.update(our_cloud) + _auth_update(cloud, our_cloud) if 'cloud' in cloud: del cloud['cloud'] @@ -186,6 +200,53 @@ class OpenStackConfig(object): new_args.update(os_args) return new_args + def _find_winning_auth_value(self, opt, config): + opt_name = opt.name.replace('-', '_') + if opt_name in config: + return config[opt_name] + else: + for d_opt in opt.deprecated_opts: + d_opt_name = d_opt.name.replace('-', '_') + if d_opt_name in config: + return config[d_opt_name] + + def _validate_auth(self, config): + # May throw a keystoneclient.exceptions.NoMatchingPlugin + plugin_options = ksc_auth.get_plugin_class( + config['auth_plugin']).get_options() + + for p_opt in plugin_options: + # if it's in config.auth, win, kill it from config dict + # if it's in config and not in config.auth, move it + # deprecated loses to current + # provided beats default, deprecated or not + winning_value = self._find_winning_auth_value( + p_opt, config['auth']) + if not winning_value: + winning_value = self._find_winning_auth_value(p_opt, config) + + # if the plugin tells us that this value is required + # then error if it's doesn't exist now + if not winning_value and p_opt.required: + raise exceptions.OpenStackConfigException( + 'Unable to find auth information for cloud' + ' {cloud} in config files {files}' + ' or environment variables. Missing value {auth_key}' + ' required for auth plugin {plugin}'.format( + cloud=cloud, files=','.join(self._config_files), + auth_key=p_opt.name, plugin=config['auth_plugin'])) + + # Clean up after ourselves + for opt in [p_opt.name] + [o.name for o in p_opt.deprecated_opts]: + opt = opt.replace('-', '_') + config.pop(opt, None) + config['auth'].pop(opt, None) + + if winning_value: + config['auth'][p_opt.name.replace('-', '_')] = winning_value + + return config + def get_one_cloud(self, cloud=None, validate=True, argparse=None, **kwargs): """Retrieve a single cloud configuration and merge additional options @@ -219,20 +280,8 @@ class OpenStackConfig(object): if type(config[key]) is not bool: config[key] = get_boolean(config[key]) - if validate: - for key in REQUIRED_VALUES: - if key not in config or not config[key]: - raise exceptions.OpenStackConfigException( - 'Unable to find full auth information for cloud' - ' {cloud} in config files {files}' - ' or environment variables.'.format( - cloud=cloud, files=','.join(self._config_files))) - if 'project_name' not in config and 'project_id' not in config: - raise exceptions.OpenStackConfigException( - 'Neither project_name or project_id information found' - ' for cloud {cloud} in config files {files}' - ' or environment variables.'.format( - cloud=cloud, files=','.join(self._config_files))) + if validate and ksc_auth: + config = self._validate_auth(config) # If any of the defaults reference other values, we need to expand for (key, value) in config.items(): diff --git a/os_client_config/vendors.py b/os_client_config/vendors.py index c78aaca..eca4376 100644 --- a/os_client_config/vendors.py +++ b/os_client_config/vendors.py @@ -1,3 +1,4 @@ +# flake8: noqa # Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -14,12 +15,16 @@ CLOUD_DEFAULTS = dict( hp=dict( - auth_url='https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0', + auth=dict( + auth_url='https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0', + ), region_name='region-b.geo-1', dns_service_type='hpext:dns', ), rackspace=dict( - auth_url='https://identity.api.rackspacecloud.com/v2.0/', + auth=dict( + auth_url='https://identity.api.rackspacecloud.com/v2.0/', + ), database_service_type='rax:database', image_api_version='2', ) |