Let configuration option be overridden by environment variables
authorDenis Laxalde <denis.laxalde@logilab.fr>
Fri, 23 Sep 2016 13:36:06 +0200
changeset 11476 a9f26de5ea6c
parent 11475 d2fcd81b7ca9
child 11477 3b4d41566de3
Let configuration option be overridden by environment variables Related to #13889793.
cubicweb/cwconfig.py
cubicweb/test/unittest_cwconfig.py
cubicweb/test/unittest_toolsutils.py
cubicweb/toolsutils.py
--- a/cubicweb/cwconfig.py	Fri Sep 23 09:51:10 2016 +0200
+++ b/cubicweb/cwconfig.py	Fri Sep 23 13:36:06 2016 +0200
@@ -206,7 +206,7 @@
 
 from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP,
                       ConfigurationError, Binary, _)
-from cubicweb.toolsutils import create_dir
+from cubicweb.toolsutils import create_dir, option_value_from_env
 
 CONFIGURATIONS = []
 
@@ -416,6 +416,12 @@
           'group': 'email', 'level': 3,
           }),
         )
+
+    def __getitem__(self, key):
+        """Get configuration option, by first looking at environmnent."""
+        file_value = super(CubicWebNoAppConfiguration, self).__getitem__(key)
+        return option_value_from_env(key, file_value)
+
     # static and class methods used to get instance independant resources ##
     @staticmethod
     def cubicweb_version():
--- a/cubicweb/test/unittest_cwconfig.py	Fri Sep 23 09:51:10 2016 +0200
+++ b/cubicweb/test/unittest_cwconfig.py	Fri Sep 23 13:36:06 2016 +0200
@@ -203,6 +203,15 @@
         from cubes import file
         self.assertEqual(file.__path__, [join(self.custom_cubes_dir, 'file')])
 
+    def test_config_value_from_environment(self):
+        self.assertIsNone(self.config['base-url'])
+        os.environ['CW_BASE_URL'] = 'https://www.cubicweb.org'
+        try:
+            self.assertEqual(self.config['base-url'],
+                             'https://www.cubicweb.org')
+        finally:
+            del os.environ['CW_BASE_URL']
+
 
 class FindPrefixTC(unittest.TestCase):
     def make_dirs(self, *args):
--- a/cubicweb/test/unittest_toolsutils.py	Fri Sep 23 09:51:10 2016 +0200
+++ b/cubicweb/test/unittest_toolsutils.py	Fri Sep 23 13:36:06 2016 +0200
@@ -17,10 +17,12 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
 
+import os
 import tempfile
 import unittest
 
-from cubicweb.toolsutils import RQLExecuteMatcher, read_config
+from cubicweb.toolsutils import (RQLExecuteMatcher, option_value_from_env,
+                                 read_config)
 
 
 class RQLExecuteMatcherTests(unittest.TestCase):
@@ -78,6 +80,15 @@
 
 class ToolsUtilsTC(unittest.TestCase):
 
+    def test_option_value_from_env(self):
+        os.environ['CW_DB_HOST'] = 'here'
+        try:
+            self.assertEqual(option_value_from_env('db-host'), 'here')
+            self.assertEqual(option_value_from_env('db-host', 'nothere'), 'here')
+            self.assertEqual(option_value_from_env('db-hots', 'nothere'), 'nothere')
+        finally:
+            del os.environ['CW_DB_HOST']
+
     def test_read_config(self):
         with tempfile.NamedTemporaryFile() as f:
             f.write(SOURCES_CONTENT)
@@ -96,6 +107,17 @@
         }
         self.assertEqual(config, expected)
 
+    def test_read_config_env(self):
+        os.environ['CW_DB_HOST'] = 'here'
+        try:
+            with tempfile.NamedTemporaryFile() as f:
+                f.write(SOURCES_CONTENT)
+                f.seek(0)
+                config = read_config(f.name)
+        finally:
+            del os.environ['CW_DB_HOST']
+        self.assertEqual(config['system']['db-host'], 'here')
+
 
 if __name__ == '__main__':
     unittest.main()
--- a/cubicweb/toolsutils.py	Fri Sep 23 09:51:10 2016 +0200
+++ b/cubicweb/toolsutils.py	Fri Sep 23 13:36:06 2016 +0200
@@ -170,6 +170,15 @@
         print('-> set permissions to 0600 for %s' % filepath)
     chmod(filepath, 0o600)
 
+
+def option_value_from_env(option, default=None):
+    """Return the value of configuration `option` from cannonical environment
+    variable.
+    """
+    envvar = ('CW_' + '_'.join(option.split('-'))).upper()
+    return os.environ.get(envvar, default)
+
+
 def read_config(config_file, raise_if_unreadable=False):
     """read some simple configuration from `config_file` and return it as a
     dictionary. If `raise_if_unreadable` is false (the default), an empty
@@ -194,7 +203,7 @@
                 sys.stderr.write('ignoring malformed line\n%r\n' % line)
                 continue
             option = option.strip().replace(' ', '_')
-            value = value.strip()
+            value = option_value_from_env(option, value.strip())
             current[option] = value or None
     except IOError as ex:
         if raise_if_unreadable: