Browse Source

Code style, rename command-line flag, and move new code into its own file (#546)

Dan Helfman 3 years ago
parent
commit
aecb6fcd74

+ 4 - 0
NEWS

@@ -1,3 +1,7 @@
+1.6.4.dev0
+ * #546: Substitute an environment variable anywhere in a borgmatic configuration option value with
+   new "${MY_ENV_VAR}" syntax.
+
 1.6.3
 1.6.3
  * #541: Add "borgmatic list --find" flag for searching for files across multiple archives, useful
  * #541: Add "borgmatic list --find" flag for searching for files across multiple archives, useful
    for hunting down that file you accidentally deleted so you can extract it. See the documentation
    for hunting down that file you accidentally deleted so you can extract it. See the documentation

+ 1 - 1
borgmatic/commands/arguments.py

@@ -189,7 +189,7 @@ def make_parsers():
         help='One or more configuration file options to override with specified values',
         help='One or more configuration file options to override with specified values',
     )
     )
     global_group.add_argument(
     global_group.add_argument(
-        '--no-env',
+        '--no-environment-interpolation',
         dest='resolve_env',
         dest='resolve_env',
         action='store_false',
         action='store_false',
         help='Do not resolve environment variables in configuration file',
         help='Do not resolve environment variables in configuration file',

+ 37 - 0
borgmatic/config/environment.py

@@ -0,0 +1,37 @@
+import os
+import re
+
+
+_VARIABLE_PATTERN = re.compile(r'(?<!\\)\$\{(?P<name>[A-Za-z0-9_]+)((:?-)(?P<default>[^}]+))?\}')
+
+
+def _resolve_string(matcher):
+    '''
+    Get the value from environment given a matcher containing a name and an optional default value.
+    If the variable is not defined in environment and no default value is provided, an Error is raised.
+    '''
+    name, default = matcher.group("name"), matcher.group("default")
+    out = os.getenv(name, default=default)
+    if out is None:
+        raise ValueError("Cannot find variable ${name} in environment".format(name=name))
+    return out
+
+
+def resolve_env_variables(item):
+    '''
+    Resolves variables like or ${FOO} from given configuration with values from process environment
+    Supported formats:
+     - ${FOO} will return FOO env variable
+     - ${FOO-bar} or ${FOO:-bar} will return FOO env variable if it exists, else "bar"
+
+    If any variable is missing in environment and no default value is provided, an Error is raised.
+    '''
+    if isinstance(item, str):
+        return _VARIABLE_PATTERN.sub(_resolve_string, item)
+    if isinstance(item, list):
+        for i, subitem in enumerate(item):
+            item[i] = resolve_env_variables(subitem)
+    if isinstance(item, dict):
+        for key, value in item.items():
+            item[key] = resolve_env_variables(value)
+    return item

+ 0 - 36
borgmatic/config/override.py

@@ -1,11 +1,7 @@
 import io
 import io
-import os
-import re
 
 
 import ruamel.yaml
 import ruamel.yaml
 
 
-_VARIABLE_PATTERN = re.compile(r'(?<!\\)\$\{(?P<name>[A-Za-z0-9_]+)((:?-)(?P<default>[^}]+))?\}')
-
 
 
 def set_values(config, keys, value):
 def set_values(config, keys, value):
     '''
     '''
@@ -81,35 +77,3 @@ def apply_overrides(config, raw_overrides):
 
 
     for (keys, value) in overrides:
     for (keys, value) in overrides:
         set_values(config, keys, value)
         set_values(config, keys, value)
-
-
-def _resolve_string(matcher):
-    '''
-    Get the value from environment given a matcher containing a name and an optional default value.
-    If the variable is not defined in environment and no default value is provided, an Error is raised.
-    '''
-    name, default = matcher.group("name"), matcher.group("default")
-    out = os.getenv(name, default=default)
-    if out is None:
-        raise ValueError("Cannot find variable ${name} in envivonment".format(name=name))
-    return out
-
-
-def resolve_env_variables(item):
-    '''
-    Resolves variables like or ${FOO} from given configuration with values from process environment
-    Supported formats:
-     - ${FOO} will return FOO env variable
-     - ${FOO-bar} or ${FOO:-bar} will return FOO env variable if it exists, else "bar"
-
-    If any variable is missing in environment and no default value is provided, an Error is raised.
-    '''
-    if isinstance(item, str):
-        return _VARIABLE_PATTERN.sub(_resolve_string, item)
-    if isinstance(item, list):
-        for i, subitem in enumerate(item):
-            item[i] = resolve_env_variables(subitem)
-    if isinstance(item, dict):
-        for key, value in item.items():
-            item[key] = resolve_env_variables(value)
-    return item

+ 3 - 3
borgmatic/config/validate.py

@@ -4,7 +4,7 @@ import jsonschema
 import pkg_resources
 import pkg_resources
 import ruamel.yaml
 import ruamel.yaml
 
 
-from borgmatic.config import load, normalize, override
+from borgmatic.config import environment, load, normalize, override
 
 
 
 
 def schema_filename():
 def schema_filename():
@@ -98,10 +98,10 @@ def parse_configuration(config_filename, schema_filename, overrides=None, resolv
     except (ruamel.yaml.error.YAMLError, RecursionError) as error:
     except (ruamel.yaml.error.YAMLError, RecursionError) as error:
         raise Validation_error(config_filename, (str(error),))
         raise Validation_error(config_filename, (str(error),))
 
 
+    normalize.normalize(config)
     override.apply_overrides(config, overrides)
     override.apply_overrides(config, overrides)
     if resolve_env:
     if resolve_env:
-        override.resolve_env_variables(config)
-    normalize.normalize(config)
+        environment.resolve_env_variables(config)
 
 
     try:
     try:
         validator = jsonschema.Draft7Validator(schema)
         validator = jsonschema.Draft7Validator(schema)

+ 1 - 1
setup.py

@@ -1,6 +1,6 @@
 from setuptools import find_packages, setup
 from setuptools import find_packages, setup
 
 
-VERSION = '1.6.3'
+VERSION = '1.6.4.dev0'
 
 
 
 
 setup(
 setup(

+ 9 - 9
tests/unit/config/test_env_variables.py → tests/unit/config/test_environment.py

@@ -1,39 +1,39 @@
 import pytest
 import pytest
 
 
-from borgmatic.config import override as module
+from borgmatic.config import environment as module
 
 
 
 
 def test_env(monkeypatch):
 def test_env(monkeypatch):
-    monkeypatch.setenv("MY_CUSTOM_VALUE", "foo")
+    monkeypatch.setenv('MY_CUSTOM_VALUE', 'foo')
     config = {'key': 'Hello $MY_CUSTOM_VALUE'}
     config = {'key': 'Hello $MY_CUSTOM_VALUE'}
     module.resolve_env_variables(config)
     module.resolve_env_variables(config)
     assert config == {'key': 'Hello $MY_CUSTOM_VALUE'}
     assert config == {'key': 'Hello $MY_CUSTOM_VALUE'}
 
 
 
 
 def test_env_braces(monkeypatch):
 def test_env_braces(monkeypatch):
-    monkeypatch.setenv("MY_CUSTOM_VALUE", "foo")
+    monkeypatch.setenv('MY_CUSTOM_VALUE', 'foo')
     config = {'key': 'Hello ${MY_CUSTOM_VALUE}'}
     config = {'key': 'Hello ${MY_CUSTOM_VALUE}'}
     module.resolve_env_variables(config)
     module.resolve_env_variables(config)
     assert config == {'key': 'Hello foo'}
     assert config == {'key': 'Hello foo'}
 
 
 
 
 def test_env_default_value(monkeypatch):
 def test_env_default_value(monkeypatch):
-    monkeypatch.delenv("MY_CUSTOM_VALUE", raising=False)
+    monkeypatch.delenv('MY_CUSTOM_VALUE', raising=False)
     config = {'key': 'Hello ${MY_CUSTOM_VALUE:-bar}'}
     config = {'key': 'Hello ${MY_CUSTOM_VALUE:-bar}'}
     module.resolve_env_variables(config)
     module.resolve_env_variables(config)
     assert config == {'key': 'Hello bar'}
     assert config == {'key': 'Hello bar'}
 
 
 
 
 def test_env_unknown(monkeypatch):
 def test_env_unknown(monkeypatch):
-    monkeypatch.delenv("MY_CUSTOM_VALUE", raising=False)
+    monkeypatch.delenv('MY_CUSTOM_VALUE', raising=False)
     config = {'key': 'Hello ${MY_CUSTOM_VALUE}'}
     config = {'key': 'Hello ${MY_CUSTOM_VALUE}'}
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
         module.resolve_env_variables(config)
         module.resolve_env_variables(config)
 
 
 
 
 def test_env_full(monkeypatch):
 def test_env_full(monkeypatch):
-    monkeypatch.setenv("MY_CUSTOM_VALUE", "foo")
-    monkeypatch.delenv("MY_CUSTOM_VALUE2", raising=False)
+    monkeypatch.setenv('MY_CUSTOM_VALUE', 'foo')
+    monkeypatch.delenv('MY_CUSTOM_VALUE2', raising=False)
     config = {
     config = {
         'key': 'Hello $MY_CUSTOM_VALUE is not resolved',
         'key': 'Hello $MY_CUSTOM_VALUE is not resolved',
         'dict': {
         'dict': {
@@ -62,8 +62,8 @@ def test_env_full(monkeypatch):
             'anotherdict': {
             'anotherdict': {
                 'key': 'My foo here',
                 'key': 'My foo here',
                 'other': 'foo',
                 'other': 'foo',
-                'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config',],
+                'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config'],
             },
             },
         },
         },
-        'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config',],
+        'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config'],
     }
     }