Fix escaped environment variable in configuration #549

Merged
witten merged 1 commits from essembeh/borgmatic:master into master 2022-06-23 17:16:11 +00:00
2 changed files with 23 additions and 3 deletions

View File

@ -1,7 +1,7 @@
import os import os
import re import re
_VARIABLE_PATTERN = re.compile(r'(?<!\\)\$\{(?P<name>[A-Za-z0-9_]+)((:?-)(?P<default>[^}]+))?\}') _VARIABLE_PATTERN = re.compile(r'(?P<escape>\\)?(?P<variable>\$\{(?P<name>[A-Za-z0-9_]+)((:?-)(?P<default>[^}]+))?\})')
def _resolve_string(matcher): 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. 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. 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')
Review

There's a super edge case where an escaped \${FOO:-default} would not be treated properly, but probably not worth dealing with given how unlikely it is to occur in the wild.

There's a super edge case where an escaped `\${FOO:-default}` would not be treated properly, but probably not worth dealing with given how unlikely it is to occur in the wild.
# resolve the env var
name, default = matcher.group('name'), matcher.group('default')
out = os.getenv(name, default=default) out = os.getenv(name, default=default)
if out is None: 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 return out

View File

@ -16,6 +16,20 @@ def test_env_braces(monkeypatch):
module.resolve_env_variables(config) module.resolve_env_variables(config)
assert config == {'key': 'Hello foo'} 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): def test_env_default_value(monkeypatch):
monkeypatch.delenv('MY_CUSTOM_VALUE', raising=False) monkeypatch.delenv('MY_CUSTOM_VALUE', raising=False)
@ -41,6 +55,7 @@ def test_env_full(monkeypatch):
'anotherdict': { 'anotherdict': {
'key': 'My ${MY_CUSTOM_VALUE} here', 'key': 'My ${MY_CUSTOM_VALUE} here',
'other': '${MY_CUSTOM_VALUE}', 'other': '${MY_CUSTOM_VALUE}',
'escaped': r'\${MY_CUSTOM_VALUE}',
'list': [ 'list': [
'/home/${MY_CUSTOM_VALUE}/.local', '/home/${MY_CUSTOM_VALUE}/.local',
'/var/log/', '/var/log/',
@ -62,6 +77,7 @@ def test_env_full(monkeypatch):
'anotherdict': { 'anotherdict': {
'key': 'My foo here', 'key': 'My foo here',
'other': 'foo', 'other': 'foo',
'escaped': '${MY_CUSTOM_VALUE}',
'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config'], 'list': ['/home/foo/.local', '/var/log/', '/home/bar/.config'],
}, },
}, },