Merge "Add support for secure.yaml file for auth info"
This commit is contained in:
commit
caede51461
28
README.rst
28
README.rst
@ -145,6 +145,34 @@ 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.
|
||||
|
||||
Splitting Secrets
|
||||
-----------------
|
||||
|
||||
In some scenarios, such as configuragtion managment controlled environments,
|
||||
it might be eaiser to have secrets in one file and non-secrets in another.
|
||||
This is fully supported via an optional file `secure.yaml` which follows all
|
||||
the same location rules as `clouds.yaml`. It can contain anything you put
|
||||
in `clouds.yaml` and will take precedence over anything in the `clouds.yaml`
|
||||
file.
|
||||
|
||||
::
|
||||
|
||||
# clouds.yaml
|
||||
clouds:
|
||||
internap:
|
||||
profile: internap
|
||||
auth:
|
||||
username: api-55f9a00fb2619
|
||||
project_name: inap-17037
|
||||
regions:
|
||||
- ams01
|
||||
- nyj01
|
||||
# secure.yaml
|
||||
clouds:
|
||||
internap:
|
||||
auth:
|
||||
password: XXXXXXXXXXXXXXXXX
|
||||
|
||||
SSL Settings
|
||||
------------
|
||||
|
||||
|
@ -48,6 +48,11 @@ CONFIG_FILES = [
|
||||
for d in CONFIG_SEARCH_PATH
|
||||
for s in YAML_SUFFIXES + JSON_SUFFIXES
|
||||
]
|
||||
SECURE_FILES = [
|
||||
os.path.join(d, 'secure' + s)
|
||||
for d in CONFIG_SEARCH_PATH
|
||||
for s in YAML_SUFFIXES + JSON_SUFFIXES
|
||||
]
|
||||
VENDOR_FILES = [
|
||||
os.path.join(d, 'clouds-public' + s)
|
||||
for d in CONFIG_SEARCH_PATH
|
||||
@ -99,6 +104,20 @@ def _get_os_environ(envvar_prefix=None):
|
||||
return ret
|
||||
|
||||
|
||||
def _merge_clouds(old_dict, new_dict):
|
||||
"""Like dict.update, except handling nested dicts."""
|
||||
ret = old_dict.copy()
|
||||
for (k, v) in new_dict.items():
|
||||
if isinstance(v, dict):
|
||||
if k in ret:
|
||||
ret[k] = _merge_clouds(ret[k], v)
|
||||
else:
|
||||
ret[k] = v.copy()
|
||||
else:
|
||||
ret[k] = v
|
||||
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():
|
||||
@ -116,20 +135,29 @@ class OpenStackConfig(object):
|
||||
|
||||
def __init__(self, config_files=None, vendor_files=None,
|
||||
override_defaults=None, force_ipv4=None,
|
||||
envvar_prefix=None):
|
||||
envvar_prefix=None, secure_files=None):
|
||||
self._config_files = config_files or CONFIG_FILES
|
||||
self._secure_files = secure_files or SECURE_FILES
|
||||
self._vendor_files = vendor_files or VENDOR_FILES
|
||||
|
||||
config_file_override = os.environ.pop('OS_CLIENT_CONFIG_FILE', None)
|
||||
if config_file_override:
|
||||
self._config_files.insert(0, config_file_override)
|
||||
|
||||
secure_file_override = os.environ.pop('OS_CLIENT_SECURE_FILE', None)
|
||||
if secure_file_override:
|
||||
self._secure_files.insert(0, secure_file_override)
|
||||
|
||||
self.defaults = defaults.get_defaults()
|
||||
if override_defaults:
|
||||
self.defaults.update(override_defaults)
|
||||
|
||||
# First, use a config file if it exists where expected
|
||||
self.config_filename, self.cloud_config = self._load_config_file()
|
||||
_, secure_config = self._load_secure_file()
|
||||
if secure_config:
|
||||
self.cloud_config = _merge_clouds(
|
||||
self.cloud_config, secure_config)
|
||||
|
||||
if not self.cloud_config:
|
||||
self.cloud_config = {'clouds': {}}
|
||||
@ -217,6 +245,9 @@ class OpenStackConfig(object):
|
||||
def _load_config_file(self):
|
||||
return self._load_yaml_json_file(self._config_files)
|
||||
|
||||
def _load_secure_file(self):
|
||||
return self._load_yaml_json_file(self._secure_files)
|
||||
|
||||
def _load_vendor_file(self):
|
||||
return self._load_yaml_json_file(self._vendor_files)
|
||||
|
||||
|
@ -64,7 +64,6 @@ USER_CONF = {
|
||||
'auth': {
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'project_name': 'testproject',
|
||||
},
|
||||
'region-name': 'test-region',
|
||||
@ -112,6 +111,15 @@ USER_CONF = {
|
||||
}
|
||||
},
|
||||
}
|
||||
SECURE_CONF = {
|
||||
'clouds': {
|
||||
'_test_cloud_no_vendor': {
|
||||
'auth': {
|
||||
'password': 'testpass',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
NO_CONF = {
|
||||
'cache': {'max_age': 1},
|
||||
}
|
||||
@ -135,6 +143,7 @@ class TestCase(base.BaseTestCase):
|
||||
tdir = self.useFixture(fixtures.TempDir())
|
||||
conf['cache']['path'] = tdir.path
|
||||
self.cloud_yaml = _write_yaml(conf)
|
||||
self.secure_yaml = _write_yaml(SECURE_CONF)
|
||||
self.vendor_yaml = _write_yaml(VENDOR_CONF)
|
||||
self.no_yaml = _write_yaml(NO_CONF)
|
||||
|
||||
@ -155,6 +164,7 @@ class TestCase(base.BaseTestCase):
|
||||
self.assertIsNone(cc.cloud)
|
||||
self.assertIn('username', cc.auth)
|
||||
self.assertEqual('testuser', cc.auth['username'])
|
||||
self.assertEqual('testpass', cc.auth['password'])
|
||||
self.assertFalse(cc.config['image_api_use_tasks'])
|
||||
self.assertTrue('project_name' in cc.auth or 'project_id' in cc.auth)
|
||||
if 'project_name' in cc.auth:
|
||||
|
@ -30,7 +30,8 @@ class TestConfig(base.TestCase):
|
||||
|
||||
def test_get_all_clouds(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.no_yaml])
|
||||
clouds = c.get_all_clouds()
|
||||
# We add one by hand because the regions cloud is going to exist
|
||||
# twice since it has two regions in it
|
||||
@ -74,7 +75,8 @@ class TestConfig(base.TestCase):
|
||||
|
||||
def test_get_one_cloud_with_config_files(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
self.assertIsInstance(c.cloud_config, dict)
|
||||
self.assertIn('cache', c.cloud_config)
|
||||
self.assertIsInstance(c.cloud_config['cache'], dict)
|
||||
@ -129,7 +131,8 @@ class TestConfig(base.TestCase):
|
||||
|
||||
def test_fallthrough(self):
|
||||
c = config.OpenStackConfig(config_files=[self.no_yaml],
|
||||
vendor_files=[self.no_yaml])
|
||||
vendor_files=[self.no_yaml],
|
||||
secure_files=[self.no_yaml])
|
||||
for k in os.environ.keys():
|
||||
if k.startswith('OS_'):
|
||||
self.useFixture(fixtures.EnvironmentVariable(k))
|
||||
@ -137,7 +140,8 @@ class TestConfig(base.TestCase):
|
||||
|
||||
def test_prefer_ipv6_true(self):
|
||||
c = config.OpenStackConfig(config_files=[self.no_yaml],
|
||||
vendor_files=[self.no_yaml])
|
||||
vendor_files=[self.no_yaml],
|
||||
secure_files=[self.no_yaml])
|
||||
cc = c.get_one_cloud(cloud='defaults', validate=False)
|
||||
self.assertTrue(cc.prefer_ipv6)
|
||||
|
||||
@ -155,7 +159,8 @@ class TestConfig(base.TestCase):
|
||||
|
||||
def test_force_ipv4_false(self):
|
||||
c = config.OpenStackConfig(config_files=[self.no_yaml],
|
||||
vendor_files=[self.no_yaml])
|
||||
vendor_files=[self.no_yaml],
|
||||
secure_files=[self.no_yaml])
|
||||
cc = c.get_one_cloud(cloud='defaults', validate=False)
|
||||
self.assertFalse(cc.force_ipv4)
|
||||
|
||||
@ -166,7 +171,8 @@ class TestConfig(base.TestCase):
|
||||
self.assertEqual('testpass', cc.auth['password'])
|
||||
|
||||
def test_get_cloud_names(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml])
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
secure_files=[self.no_yaml])
|
||||
self.assertEqual(
|
||||
['_test-cloud-domain-id_',
|
||||
'_test-cloud-int-project_',
|
||||
@ -177,7 +183,8 @@ class TestConfig(base.TestCase):
|
||||
],
|
||||
sorted(c.get_cloud_names()))
|
||||
c = config.OpenStackConfig(config_files=[self.no_yaml],
|
||||
vendor_files=[self.no_yaml])
|
||||
vendor_files=[self.no_yaml],
|
||||
secure_files=[self.no_yaml])
|
||||
for k in os.environ.keys():
|
||||
if k.startswith('OS_'):
|
||||
self.useFixture(fixtures.EnvironmentVariable(k))
|
||||
|
@ -29,6 +29,8 @@ class TestEnviron(base.TestCase):
|
||||
fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_USERNAME', 'testuser'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PASSWORD', 'testpass'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject'))
|
||||
self.useFixture(
|
||||
@ -57,13 +59,15 @@ class TestEnviron(base.TestCase):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PREFER_IPV6', 'false'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
cc = c.get_one_cloud('_test-cloud_')
|
||||
self.assertFalse(cc.prefer_ipv6)
|
||||
|
||||
def test_environ_exists(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
cc = c.get_one_cloud('envvars')
|
||||
self._assert_cloud_details(cc)
|
||||
self.assertNotIn('auth_url', cc.config)
|
||||
@ -78,7 +82,8 @@ class TestEnviron(base.TestCase):
|
||||
def test_environ_prefix(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml],
|
||||
envvar_prefix='NOVA_')
|
||||
envvar_prefix='NOVA_',
|
||||
secure_files=[self.secure_yaml])
|
||||
cc = c.get_one_cloud('envvars')
|
||||
self._assert_cloud_details(cc)
|
||||
self.assertNotIn('auth_url', cc.config)
|
||||
@ -92,7 +97,8 @@ class TestEnviron(base.TestCase):
|
||||
|
||||
def test_get_one_cloud_with_config_files(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
self.assertIsInstance(c.cloud_config, dict)
|
||||
self.assertIn('cache', c.cloud_config)
|
||||
self.assertIsInstance(c.cloud_config['cache'], dict)
|
||||
|
Loading…
x
Reference in New Issue
Block a user