summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--os_client_config/cloud_config.py8
-rw-r--r--os_client_config/config.py88
-rw-r--r--os_client_config/tests/base.py17
-rw-r--r--os_client_config/tests/test_config.py36
-rw-r--r--requirements.txt1
5 files changed, 104 insertions, 46 deletions
diff --git a/os_client_config/cloud_config.py b/os_client_config/cloud_config.py
index f60303b..86b4f50 100644
--- a/os_client_config/cloud_config.py
+++ b/os_client_config/cloud_config.py
@@ -16,11 +16,13 @@ import warnings
class CloudConfig(object):
- def __init__(self, name, region, config, prefer_ipv6=False):
+ def __init__(self, name, region, config,
+ prefer_ipv6=False, auth_plugin=None):
self.name = name
self.region = region
self.config = config
self._prefer_ipv6 = prefer_ipv6
+ self._auth = auth_plugin
def __getattr__(self, key):
"""Return arbitrary attributes."""
@@ -106,3 +108,7 @@ class CloudConfig(object):
@property
def prefer_ipv6(self):
return self._prefer_ipv6
+
+ def get_auth(self):
+ """Return a keystoneauth plugin from the auth credentials."""
+ return self._auth
diff --git a/os_client_config/config.py b/os_client_config/config.py
index d698378..e8e76a5 100644
--- a/os_client_config/config.py
+++ b/os_client_config/config.py
@@ -17,13 +17,9 @@ import os
import warnings
import appdirs
+from keystoneauth1 import loading
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 defaults
from os_client_config import exceptions
@@ -283,15 +279,38 @@ class OpenStackConfig(object):
cloud = self._fix_backwards_project(cloud)
cloud = self._fix_backwards_auth_plugin(cloud)
cloud = self._fix_backwards_interface(cloud)
+ cloud = self._handle_domain_id(cloud)
+ return cloud
+
+ def _handle_domain_id(self, cloud):
+ # Allow people to just specify domain once if it's the same
+ mappings = {
+ 'domain_id': ('user_domain_id', 'project_domain_id'),
+ 'domain_name': ('user_domain_name', 'project_domain_name'),
+ }
+ for target_key, possible_values in mappings.items():
+ for key in possible_values:
+ if target_key in cloud['auth'] and key not in cloud['auth']:
+ cloud['auth'][key] = cloud['auth'][target_key]
+ cloud['auth'].pop(target_key, None)
return cloud
def _fix_backwards_project(self, cloud):
# Do the lists backwards so that project_name is the ultimate winner
+ # Also handle moving domain names into auth so that domain mapping
+ # is easier
mappings = {
'project_id': ('tenant_id', 'tenant-id',
'project_id', 'project-id'),
'project_name': ('tenant_name', 'tenant-name',
'project_name', 'project-name'),
+ 'domain_id': ('domain_id', 'domain-id'),
+ 'domain_name': ('domain_name', 'domain-name'),
+ 'user_domain_id': ('user_domain_id', 'user-domain-id'),
+ 'user_domain_name': ('user_domain_name', 'user-domain-name'),
+ 'project_domain_id': ('project_domain_id', 'project-domain-id'),
+ 'project_domain_name': (
+ 'project_domain_name', 'project-domain-name'),
}
for target_key, possible_values in mappings.items():
target = None
@@ -376,19 +395,27 @@ class OpenStackConfig(object):
if opt_name in config:
return config[opt_name]
else:
- for d_opt in opt.deprecated_opts:
+ for d_opt in opt.deprecated:
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
- if config['auth_type'] == 'admin_endpoint':
- auth_plugin = ksc_auth.token_endpoint.Token
- else:
- auth_plugin = ksc_auth.get_plugin_class(config['auth_type'])
+ def _get_auth_loader(self, config):
+ # Re-use the admin_token plugin for the "None" plugin
+ # since it does not look up endpoints or tokens but rather
+ # does a passthrough. This is useful for things like Ironic
+ # that have a keystoneless operational mode, but means we're
+ # still dealing with a keystoneauth Session object, so all the
+ # _other_ things (SSL arg handling, timeout) all work consistently
+ if config['auth_type'] in (None, "None", ''):
+ config['auth_type'] = 'admin_token'
+ config['auth']['token'] = None
+ return loading.get_plugin_loader(config['auth_type'])
+
+ def _validate_auth(self, config, loader):
+ # May throw a keystoneauth1.exceptions.NoMatchingPlugin
- plugin_options = auth_plugin.get_options()
+ plugin_options = loader.get_options()
for p_opt in plugin_options:
# if it's in config.auth, win, kill it from config dict
@@ -400,19 +427,8 @@ class OpenStackConfig(object):
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.get('auth_type')))
-
# Clean up after ourselves
- for opt in [p_opt.name] + [o.name for o in p_opt.deprecated_opts]:
+ for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]:
opt = opt.replace('-', '_')
config.pop(opt, None)
config['auth'].pop(opt, None)
@@ -435,13 +451,16 @@ class OpenStackConfig(object):
:param string cloud:
The name of the configuration to load from clouds.yaml
:param boolean validate:
- Validate that required arguments are present and certain
- argument combinations are valid
+ Validate the config. Setting this to False causes no auth plugin
+ to be created. It's really only useful for testing.
:param Namespace argparse:
An argparse Namespace object; allows direct passing in of
argparse options to be added to the cloud config. Values
of None and '' will be removed.
:param kwargs: Additional configuration options
+
+ :raises: keystoneauth1.exceptions.MissingRequiredOptions
+ on missing required auth parameters
"""
if cloud is None and self.envvar_key in self.get_cloud_names():
@@ -471,12 +490,12 @@ class OpenStackConfig(object):
if type(config[key]) is not bool:
config[key] = get_boolean(config[key])
- if 'auth_type' in config:
- if config['auth_type'] in ('', 'None', None):
- validate = False
-
- if validate and ksc_auth:
- config = self._validate_auth(config)
+ loader = self._get_auth_loader(config)
+ if validate:
+ config = self._validate_auth(config, loader)
+ auth_plugin = loader.load_from_options(**config['auth'])
+ else:
+ auth_plugin = None
# If any of the defaults reference other values, we need to expand
for (key, value) in config.items():
@@ -492,7 +511,8 @@ class OpenStackConfig(object):
return cloud_config.CloudConfig(
name=cloud_name, region=config['region_name'],
config=self._normalize_keys(config),
- prefer_ipv6=prefer_ipv6)
+ prefer_ipv6=prefer_ipv6,
+ auth_plugin=auth_plugin)
@staticmethod
def set_one_cloud(config_file, cloud, set_config=None):
diff --git a/os_client_config/tests/base.py b/os_client_config/tests/base.py
index 89e04c0..cbf58da 100644
--- a/os_client_config/tests/base.py
+++ b/os_client_config/tests/base.py
@@ -31,6 +31,7 @@ VENDOR_CONF = {
'public-clouds': {
'_test_cloud_in_our_cloud': {
'auth': {
+ 'auth_url': 'http://example.com/v2',
'username': 'testotheruser',
'project_name': 'testproject',
},
@@ -45,6 +46,7 @@ USER_CONF = {
'_test-cloud_': {
'profile': '_test_cloud_in_our_cloud',
'auth': {
+ 'auth_url': 'http://example.com/v2',
'username': 'testuser',
'password': 'testpass',
},
@@ -53,6 +55,7 @@ USER_CONF = {
'_test_cloud_no_vendor': {
'profile': '_test_non_existant_cloud',
'auth': {
+ 'auth_url': 'http://example.com/v2',
'username': 'testuser',
'password': 'testpass',
'project_name': 'testproject',
@@ -64,6 +67,18 @@ USER_CONF = {
'username': 'testuser',
'password': 'testpass',
'project_id': 12345,
+ 'auth_url': 'http://example.com/v2',
+ },
+ 'region_name': 'test-region',
+ },
+ '_test-cloud-domain-id_': {
+ 'auth': {
+ 'username': 'testuser',
+ 'password': 'testpass',
+ 'project_id': 12345,
+ 'auth_url': 'http://example.com/v2',
+ 'domain_id': '6789',
+ 'project_domain_id': '123456789',
},
'region_name': 'test-region',
},
@@ -72,6 +87,7 @@ USER_CONF = {
'username': 'testuser',
'password': 'testpass',
'project-id': 'testproject',
+ 'auth_url': 'http://example.com/v2',
},
'regions': [
'region1',
@@ -83,6 +99,7 @@ USER_CONF = {
'username': 'testuser',
'password': 'testpass',
'project-id': '12345',
+ 'auth_url': 'http://example.com/v2',
},
'region_name': 'test-region',
}
diff --git a/os_client_config/tests/test_config.py b/os_client_config/tests/test_config.py
index 332e4d3..36fe15d 100644
--- a/os_client_config/tests/test_config.py
+++ b/os_client_config/tests/test_config.py
@@ -43,7 +43,7 @@ class TestConfig(base.TestCase):
def test_get_one_cloud(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
- cloud = c.get_one_cloud()
+ cloud = c.get_one_cloud(validate=False)
self.assertIsInstance(cloud, cloud_config.CloudConfig)
self.assertEqual(cloud.name, '')
@@ -61,12 +61,12 @@ class TestConfig(base.TestCase):
)
def test_get_one_cloud_auth_override_defaults(self):
- default_options = {'auth_type': 'token'}
+ default_options = {'compute_api_version': '4'}
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
override_defaults=default_options)
cc = c.get_one_cloud(cloud='_test-cloud_', auth={'username': 'user'})
self.assertEqual('user', cc.auth['username'])
- self.assertEqual('token', cc.auth_type)
+ self.assertEqual('4', cc.compute_api_version)
self.assertEqual(
defaults._defaults['identity_api_version'],
cc.identity_api_version,
@@ -91,6 +91,15 @@ class TestConfig(base.TestCase):
cc = c.get_one_cloud('_test-cloud-int-project_')
self.assertEqual('12345', cc.auth['project_id'])
+ def test_get_one_cloud_with_domain_id(self):
+ c = config.OpenStackConfig(config_files=[self.cloud_yaml],
+ vendor_files=[self.vendor_yaml])
+ cc = c.get_one_cloud('_test-cloud-domain-id_')
+ self.assertEqual('6789', cc.auth['user_domain_id'])
+ self.assertEqual('123456789', cc.auth['project_domain_id'])
+ self.assertNotIn('domain_id', cc.auth)
+ self.assertNotIn('domain-id', cc.auth)
+
def test_get_one_cloud_with_hyphenated_project_id(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
@@ -109,7 +118,7 @@ class TestConfig(base.TestCase):
for k in os.environ.keys():
if k.startswith('OS_'):
self.useFixture(fixtures.EnvironmentVariable(k))
- c.get_one_cloud(cloud='defaults')
+ c.get_one_cloud(cloud='defaults', validate=False)
def test_prefer_ipv6_true(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
@@ -120,7 +129,7 @@ class TestConfig(base.TestCase):
def test_prefer_ipv6_false(self):
c = config.OpenStackConfig(config_files=[self.no_yaml],
vendor_files=[self.no_yaml])
- cc = c.get_one_cloud(cloud='defaults')
+ cc = c.get_one_cloud(cloud='defaults', validate=False)
self.assertFalse(cc.prefer_ipv6)
def test_get_one_cloud_auth_merge(self):
@@ -132,7 +141,8 @@ class TestConfig(base.TestCase):
def test_get_cloud_names(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml])
self.assertEqual(
- ['_test-cloud-int-project_',
+ ['_test-cloud-domain-id_',
+ '_test-cloud-int-project_',
'_test-cloud_',
'_test_cloud_hyphenated',
'_test_cloud_no_vendor',
@@ -144,7 +154,7 @@ class TestConfig(base.TestCase):
for k in os.environ.keys():
if k.startswith('OS_'):
self.useFixture(fixtures.EnvironmentVariable(k))
- c.get_one_cloud(cloud='defaults')
+ c.get_one_cloud(cloud='defaults', validate=False)
self.assertEqual(['defaults'], sorted(c.get_cloud_names()))
def test_set_one_cloud_creates_file(self):
@@ -168,7 +178,8 @@ class TestConfig(base.TestCase):
resulting_cloud_config = {
'auth': {
'password': 'newpass',
- 'username': 'testuser'
+ 'username': 'testuser',
+ 'auth_url': 'http://example.com/v2',
},
'cloud': 'new_cloud',
'profile': '_test_cloud_in_our_cloud',
@@ -189,6 +200,10 @@ class TestConfigArgparse(base.TestCase):
super(TestConfigArgparse, self).setUp()
self.options = argparse.Namespace(
+ auth_url='http://example.com/v2',
+ username='user',
+ password='password',
+ project_name='project',
region_name='other-test-region',
snack_type='cookie',
)
@@ -208,7 +223,6 @@ class TestConfigArgparse(base.TestCase):
cc = c.get_one_cloud(cloud='', argparse=self.options)
self.assertIsNone(cc.cloud)
- self.assertNotIn('username', cc.auth)
self.assertEqual(cc.region_name, 'other-test-region')
self.assertEqual(cc.snack_type, 'cookie')
@@ -270,11 +284,11 @@ class TestConfigDefault(base.TestCase):
self.assertEqual('password', cc.auth_type)
def test_set_default_before_init(self):
- config.set_default('auth_type', 'token')
+ config.set_default('identity_api_version', '4')
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
cc = c.get_one_cloud(cloud='_test-cloud_', argparse=None)
- self.assertEqual('token', cc.auth_type)
+ self.assertEqual('4', cc.identity_api_version)
class TestBackwardsCompatibility(base.TestCase):
diff --git a/requirements.txt b/requirements.txt
index 894a70a..db0b635 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@
# process, which may cause wedges in the gate later.
PyYAML>=3.1.0
appdirs>=1.3.0
+keystoneauth1>=1.0.0