From b63c854509ac9e4e9b7c9b01939c94548a92dcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20MB?= Date: Fri, 17 Jun 2022 09:50:56 +0200 Subject: [PATCH] Fix escaped environment variable in configuration - when an env variable is escaped in the configuration file, we expect not to resolve it and remove the escape char `\` --- borgmatic/config/environment.py | 10 +++++++--- tests/unit/config/test_environment.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/borgmatic/config/environment.py b/borgmatic/config/environment.py index 49d673b..f79dfc9 100644 --- a/borgmatic/config/environment.py +++ b/borgmatic/config/environment.py @@ -1,7 +1,7 @@ import os import re -_VARIABLE_PATTERN = re.compile(r'(?[A-Za-z0-9_]+)((:?-)(?P[^}]+))?\}') +_VARIABLE_PATTERN = re.compile(r'(?P\\)?(?P\$\{(?P[A-Za-z0-9_]+)((:?-)(?P[^}]+))?\})') def _resolve_string(matcher): @@ -9,10 +9,14 @@ 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") + if matcher.group('escape') is not None: + # in case of escaped envvar, unescape it + return matcher.group('variable') + # resolve the env var + 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)) + raise ValueError('Cannot find variable ${name} in environment'.format(name=name)) return out diff --git a/tests/unit/config/test_environment.py b/tests/unit/config/test_environment.py index cf164ba..6f5d1df 100644 --- a/tests/unit/config/test_environment.py +++ b/tests/unit/config/test_environment.py @@ -16,6 +16,20 @@ def test_env_braces(monkeypatch): module.resolve_env_variables(config) assert config == {'key': 'Hello foo'} +def test_env_multi(monkeypatch): + monkeypatch.setenv('MY_CUSTOM_VALUE', 'foo') + monkeypatch.setenv('MY_CUSTOM_VALUE2', 'bar') + config = {'key': 'Hello ${MY_CUSTOM_VALUE}${MY_CUSTOM_VALUE2}'} + module.resolve_env_variables(config) + assert config == {'key': 'Hello foobar'} + +def test_env_escape(monkeypatch): + monkeypatch.setenv('MY_CUSTOM_VALUE', 'foo') + monkeypatch.setenv('MY_CUSTOM_VALUE2', 'bar') + config = {'key': r'Hello ${MY_CUSTOM_VALUE} \${MY_CUSTOM_VALUE}'} + module.resolve_env_variables(config) + assert config == {'key': r'Hello foo ${MY_CUSTOM_VALUE}'} + def test_env_default_value(monkeypatch): monkeypatch.delenv('MY_CUSTOM_VALUE', raising=False) @@ -41,6 +55,7 @@ def test_env_full(monkeypatch): 'anotherdict': { 'key': 'My ${MY_CUSTOM_VALUE} here', 'other': '${MY_CUSTOM_VALUE}', + 'escaped': r'\${MY_CUSTOM_VALUE}', 'list': [ '/home/${MY_CUSTOM_VALUE}/.local', '/var/log/', @@ -62,6 +77,7 @@ def test_env_full(monkeypatch): 'anotherdict': { 'key': 'My foo here', 'other': 'foo', + 'escaped': '${MY_CUSTOM_VALUE}', 'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config'], }, },