summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMonty Taylor <mordred@inaugust.com>2015-04-11 08:27:05 -0400
committerMonty Taylor <mordred@inaugust.com>2015-04-11 13:36:55 -0400
commit7e682d3bf097a006ec43c16ecc96664bf4b29294 (patch)
treeff93d4fc77133d58c2a30a2a092bc8c5293d1f5c
parent2ac9258563a969283472fbde053850eccbc2fbf2 (diff)
downloados-client-config-7e682d3bf097a006ec43c16ecc96664bf4b29294.tar.gz
Put env vars into their own cloud config
The semantics around mixing environment variables and config file values are confusing at best and no reasonable usecase has been expressed as to why doing so is desirable. Move the logic around environment variable processing to always provide an "envvars" cloud if any envvars are set. The cloud will only exist in the presence of OS_ env vars. get_one_cloud() will default to returning the envvars cloud if it exists. Change-Id: I6c3a54997c3278feedfdf93cc4d1e74b6235700a Closes-Bug: #1439927
-rw-r--r--README.rst15
-rw-r--r--os_client_config/cloud_config.py2
-rw-r--r--os_client_config/config.py31
-rw-r--r--os_client_config/tests/base.py80
-rw-r--r--os_client_config/tests/test_config.py67
-rw-r--r--os_client_config/tests/test_environ.py67
-rw-r--r--os_client_config/tests/test_os_client_config.py28
7 files changed, 181 insertions, 109 deletions
diff --git a/README.rst b/README.rst
index 4c67acf..18bf81d 100644
--- a/README.rst
+++ b/README.rst
@@ -16,14 +16,13 @@ os-client-config honors all of the normal `OS_*` variables. It does not
provide backwards compatibility to service-specific variables such as
`NOVA_USERNAME`.
-If you have environment variables and no config files, os-client-config
-will produce a cloud config object named "openstack" containing your
-values from the environment.
+If you have OpenStack environment variables seet and no config files,
+os-client-config will produce a cloud config object named "envvars" containing
+your values from the environment.
Service specific settings, like the nova service type, are set with the
default service type as a prefix. For instance, to set a special service_type
-for trove (because you're using Rackspace) set:
-::
+for trove set::
export OS_DATABASE_SERVICE_TYPE=rax:database
@@ -40,7 +39,7 @@ locations:
The first file found wins.
The keys are all of the keys you'd expect from `OS_*` - except lower case
-and without the OS prefix. So, username is set with `username`.
+and without the OS prefix. So, region name is set with `region_name`.
Service specific settings, like the nova service type, are set with the
default service type as a prefix. For instance, to set a special service_type
@@ -119,6 +118,10 @@ behaviors:
* `cache.max_age` and nothing else gets you memory cache.
* Otherwise, `cache.class` and `cache.arguments` are passed in
+`os-client-config` does not actually cache anything itself, but it collects
+and presents the cache information so that your various applications that
+are connecting to OpenStack can share a cache should you desire.
+
::
cache:
diff --git a/os_client_config/cloud_config.py b/os_client_config/cloud_config.py
index 6f501c6..c17bfc2 100644
--- a/os_client_config/cloud_config.py
+++ b/os_client_config/cloud_config.py
@@ -15,7 +15,7 @@
class CloudConfig(object):
def __init__(self, name, region, config):
- self.name = name or 'openstack'
+ self.name = name
self.region = region
self.config = config
diff --git a/os_client_config/config.py b/os_client_config/config.py
index 8494043..70326cf 100644
--- a/os_client_config/config.py
+++ b/os_client_config/config.py
@@ -54,10 +54,12 @@ def get_boolean(value):
def _get_os_environ():
ret = dict(defaults._defaults)
- for (k, v) in os.environ.items():
- if k.startswith('OS_'):
- newkey = k[3:].lower()
- ret[newkey] = v
+ environkeys = [k for k in os.environ.keys() if k.startswith('OS_')]
+ if not environkeys:
+ return None
+ for k in environkeys:
+ newkey = k[3:].lower()
+ ret[newkey] = os.environ[k]
return ret
@@ -80,7 +82,7 @@ class OpenStackConfig(object):
self._config_files = config_files or CONFIG_FILES
self._vendor_files = vendor_files or VENDOR_FILES
- self.defaults = _get_os_environ()
+ self.defaults = dict(defaults._defaults)
# use a config file if it exists where expected
self.cloud_config = self._load_config_file()
@@ -88,6 +90,14 @@ class OpenStackConfig(object):
self.cloud_config = dict(
clouds=dict(openstack=dict(self.defaults)))
+ envvars = _get_os_environ()
+ if envvars:
+ if 'envvars' in self.cloud_config['clouds']:
+ raise exceptions.OpenStackConfigException(
+ 'clouds.yaml defines a cloud named envvars, and OS_'
+ ' env vars are set')
+ self.cloud_config['clouds']['envvars'] = envvars
+
self._cache_max_age = None
self._cache_path = CACHE_PATH
self._cache_class = 'dogpile.cache.null'
@@ -109,6 +119,7 @@ class OpenStackConfig(object):
if os.path.exists(path):
with open(path, 'r') as f:
return yaml.safe_load(f)
+ return dict(clouds=dict())
def _load_vendor_file(self):
for path in self._vendor_files:
@@ -135,7 +146,7 @@ class OpenStackConfig(object):
# No region configured
return ''
- def _get_region(self, cloud):
+ def _get_region(self, cloud=None):
return self._get_regions(cloud).split(',')[0]
def _get_cloud_sections(self):
@@ -152,7 +163,7 @@ class OpenStackConfig(object):
our_cloud = self.cloud_config['clouds'].get(name, dict())
- # Get the defaults (including env vars) first
+ # Get the defaults
cloud.update(self.defaults)
# yes, I know the next line looks silly
@@ -197,7 +208,8 @@ class OpenStackConfig(object):
if key in cloud['auth']:
target = cloud['auth'][key]
del cloud['auth'][key]
- cloud['auth'][target_key] = target
+ if target:
+ cloud['auth'][target_key] = target
return cloud
def _fix_backwards_auth_plugin(self, cloud):
@@ -321,6 +333,9 @@ class OpenStackConfig(object):
:param kwargs: Additional configuration options
"""
+ if cloud is None and 'envvars' in self._get_cloud_sections():
+ cloud = 'envvars'
+
args = self._fix_args(kwargs, argparse=argparse)
if 'region_name' not in args or args['region_name'] is None:
diff --git a/os_client_config/tests/base.py b/os_client_config/tests/base.py
index 1c30cdb..f6e815d 100644
--- a/os_client_config/tests/base.py
+++ b/os_client_config/tests/base.py
@@ -15,9 +15,87 @@
# License for the specific language governing permissions and limitations
# under the License.
+
+import os
+import tempfile
+
+from os_client_config import cloud_config
+
+import extras
+import fixtures
from oslotest import base
+import yaml
-class TestCase(base.BaseTestCase):
+VENDOR_CONF = {
+ 'public-clouds': {
+ '_test_cloud_in_our_cloud': {
+ 'auth': {
+ 'username': 'testotheruser',
+ 'project_name': 'testproject',
+ },
+ },
+ }
+}
+USER_CONF = {
+ 'clouds': {
+ '_test_cloud_': {
+ 'cloud': '_test_cloud_in_our_cloud',
+ 'auth': {
+ 'username': 'testuser',
+ 'password': 'testpass',
+ },
+ 'region_name': 'test-region',
+ },
+ '_test_cloud_no_vendor': {
+ 'cloud': '_test_non_existant_cloud',
+ 'auth': {
+ 'username': 'testuser',
+ 'password': 'testpass',
+ 'project_name': 'testproject',
+ },
+ 'region_name': 'test-region',
+ },
+ },
+ 'cache': {'max_age': 1},
+}
+
+
+def _write_yaml(obj):
+ # Assume NestedTempfile so we don't have to cleanup
+ with tempfile.NamedTemporaryFile(delete=False) as obj_yaml:
+ obj_yaml.write(yaml.safe_dump(obj).encode('utf-8'))
+ return obj_yaml.name
+
+class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""
+
+ def setUp(self):
+ super(TestCase, self).setUp()
+
+ self.useFixture(fixtures.NestedTempfile())
+ conf = dict(USER_CONF)
+ tdir = self.useFixture(fixtures.TempDir())
+ conf['cache']['path'] = tdir.path
+ self.cloud_yaml = _write_yaml(conf)
+ self.vendor_yaml = _write_yaml(VENDOR_CONF)
+
+ # Isolate the test runs from the environment
+ # Do this as two loops because you can't modify the dict in a loop
+ # over the dict in 3.4
+ keys_to_isolate = []
+ for env in os.environ.keys():
+ if env.startswith('OS_'):
+ keys_to_isolate.append(env)
+ for env in keys_to_isolate:
+ self.useFixture(fixtures.EnvironmentVariable(env))
+
+ def _assert_cloud_details(self, cc):
+ self.assertIsInstance(cc, cloud_config.CloudConfig)
+ self.assertTrue(extras.safe_hasattr(cc, 'auth'))
+ self.assertIsInstance(cc.auth, dict)
+ self.assertIsNone(cc.cloud)
+ self.assertIn('username', cc.auth)
+ self.assertEqual('testuser', cc.auth['username'])
+ self.assertEqual('testproject', cc.auth['project_name'])
diff --git a/os_client_config/tests/test_config.py b/os_client_config/tests/test_config.py
index 5ef3048..9decf3e 100644
--- a/os_client_config/tests/test_config.py
+++ b/os_client_config/tests/test_config.py
@@ -12,66 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import tempfile
-
-import extras
-import fixtures
-import testtools
-import yaml
-
from os_client_config import cloud_config
from os_client_config import config
-
-VENDOR_CONF = {
- 'public-clouds': {
- '_test_cloud_in_our_cloud': {
- 'auth': {
- 'username': 'testotheruser',
- 'project_name': 'testproject',
- },
- },
- }
-}
-USER_CONF = {
- 'clouds': {
- '_test_cloud_': {
- 'cloud': '_test_cloud_in_our_cloud',
- 'auth': {
- 'username': 'testuser',
- 'password': 'testpass',
- },
- 'region_name': 'test-region',
- },
- '_test_cloud_no_vendor': {
- 'cloud': '_test_non_existant_cloud',
- 'auth': {
- 'username': 'testuser',
- 'password': 'testpass',
- 'project_name': 'testproject',
- },
- 'region_name': 'test-region',
- },
- },
- 'cache': {'max_age': 1},
-}
+from os_client_config.tests import base
-def _write_yaml(obj):
- # Assume NestedTempfile so we don't have to cleanup
- with tempfile.NamedTemporaryFile(delete=False) as obj_yaml:
- obj_yaml.write(yaml.safe_dump(obj).encode('utf-8'))
- return obj_yaml.name
-
-
-class TestConfig(testtools.TestCase):
- def setUp(self):
- super(TestConfig, self).setUp()
- self.useFixture(fixtures.NestedTempfile())
- conf = dict(USER_CONF)
- tdir = self.useFixture(fixtures.TempDir())
- conf['cache']['path'] = tdir.path
- self.cloud_yaml = _write_yaml(conf)
- self.vendor_yaml = _write_yaml(VENDOR_CONF)
+class TestConfig(base.TestCase):
def test_get_one_cloud(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
@@ -90,12 +36,3 @@ class TestConfig(testtools.TestCase):
self._assert_cloud_details(cc)
cc = c.get_one_cloud('_test_cloud_no_vendor')
self._assert_cloud_details(cc)
-
- def _assert_cloud_details(self, cc):
- self.assertIsInstance(cc, cloud_config.CloudConfig)
- self.assertTrue(extras.safe_hasattr(cc, 'auth'))
- self.assertIsInstance(cc.auth, dict)
- self.assertIsNone(cc.cloud)
- self.assertIn('username', cc.auth)
- self.assertEqual('testuser', cc.auth['username'])
- self.assertEqual('testproject', cc.auth['project_name'])
diff --git a/os_client_config/tests/test_environ.py b/os_client_config/tests/test_environ.py
new file mode 100644
index 0000000..473c417
--- /dev/null
+++ b/os_client_config/tests/test_environ.py
@@ -0,0 +1,67 @@
+# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
+#
+# 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.
+
+
+from os_client_config import cloud_config
+from os_client_config import config
+from os_client_config import exceptions
+from os_client_config.tests import base
+
+import fixtures
+
+
+class TestConfig(base.TestCase):
+
+ def test_get_one_cloud(self):
+ c = config.OpenStackConfig(config_files=[self.cloud_yaml],
+ vendor_files=[self.vendor_yaml])
+ self.assertIsInstance(c.get_one_cloud(), cloud_config.CloudConfig)
+
+ def test_no_environ(self):
+ c = config.OpenStackConfig(config_files=[self.cloud_yaml],
+ vendor_files=[self.vendor_yaml])
+ self.assertRaises(
+ exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars')
+
+ def test_environ_exists(self):
+ self.useFixture(
+ fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com'))
+ self.useFixture(
+ fixtures.EnvironmentVariable('OS_USERNAME', 'testuser'))
+ self.useFixture(
+ fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject'))
+ c = config.OpenStackConfig(config_files=[self.cloud_yaml],
+ vendor_files=[self.vendor_yaml])
+ cc = c.get_one_cloud('envvars')
+ self._assert_cloud_details(cc)
+ self.assertNotIn('auth_url', cc.config)
+ self.assertIn('auth_url', cc.config['auth'])
+ self.assertNotIn('auth_url', cc.config)
+ cc = c.get_one_cloud('_test_cloud_')
+ self._assert_cloud_details(cc)
+ cc = c.get_one_cloud('_test_cloud_no_vendor')
+ self._assert_cloud_details(cc)
+
+ def test_get_one_cloud_with_config_files(self):
+ c = config.OpenStackConfig(config_files=[self.cloud_yaml],
+ vendor_files=[self.vendor_yaml])
+ self.assertIsInstance(c.cloud_config, dict)
+ self.assertIn('cache', c.cloud_config)
+ self.assertIsInstance(c.cloud_config['cache'], dict)
+ self.assertIn('max_age', c.cloud_config['cache'])
+ self.assertIn('path', c.cloud_config['cache'])
+ cc = c.get_one_cloud('_test_cloud_')
+ self._assert_cloud_details(cc)
+ cc = c.get_one_cloud('_test_cloud_no_vendor')
+ self._assert_cloud_details(cc)
diff --git a/os_client_config/tests/test_os_client_config.py b/os_client_config/tests/test_os_client_config.py
deleted file mode 100644
index 7421b6f..0000000
--- a/os_client_config/tests/test_os_client_config.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# 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.
-
-"""
-test_os_client_config
-----------------------------------
-
-Tests for `os_client_config` module.
-"""
-
-from os_client_config.tests import base
-
-
-class TestOs_client_config(base.TestCase):
-
- def test_something(self):
- pass