Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions compose/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,13 @@ def parse_ulimits(ulimits):
def resolve_env_var(key, val, environment):
if val is not None:
return key, val
elif environment and key.endswith('?'):
required_key = key[:-1]
if required_key not in environment:
raise ConfigurationError(
'environment variable "{key}" is required'.format(key=key))
else:
return required_key, environment[required_key]
elif environment and key in environment:
return key, environment[key]
else:
Expand Down
22 changes: 16 additions & 6 deletions compose/config/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def __init__(self, templater, mapping):
def interpolate(self, string):
try:
return self.templater(string).substitute(self.mapping)
except ValueError:
raise InvalidInterpolation(string)
except ValueError as e:
raise InvalidInterpolation(string, e)


def interpolate_environment_variables(version, config, section, environment):
Expand All @@ -51,11 +51,12 @@ def interpolate_value(name, config_key, value, section, interpolator):
except InvalidInterpolation as e:
raise ConfigurationError(
'Invalid interpolation format for "{config_key}" option '
'in {section} "{name}": "{string}"'.format(
'in {section} "{name}": "{string}" caused by {value_error}'.format(
config_key=config_key,
name=name,
section=section,
string=e.string))
string=e.string,
value_error=e.value_error))


def recursive_interpolate(obj, interpolator):
Expand All @@ -72,15 +73,23 @@ def recursive_interpolate(obj, interpolator):


class TemplateWithDefaults(Template):
idpattern = r'[_a-z][_a-z0-9]*(?::?-[^}]+)?'
idpattern = r'[_a-z][_a-z0-9]*(?::?-[^}]+)?\??'

# Modified from python2.7/string.py
def substitute(self, mapping):

# Helper function for .sub()
def convert(mo):
# Check the most common path first.
named = mo.group('named') or mo.group('braced')
if named is not None:
if named.endswith('?'):
var = named[:-1]
value = mapping.get(var)
if not value:
raise ValueError('required environment variable "{var}" not set'
.format(var=var))
return value
if ':-' in named:
var, _, default = named.partition(':-')
return mapping.get(var) or default
Expand All @@ -99,5 +108,6 @@ def convert(mo):


class InvalidInterpolation(Exception):
def __init__(self, string):
def __init__(self, string, value_error):
self.string = string
self.value_error = value_error
9 changes: 9 additions & 0 deletions tests/unit/config/interpolation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,12 @@ def test_interpolate_missing_with_default(defaults_interpolator):
def test_interpolate_with_empty_and_default_value(defaults_interpolator):
assert defaults_interpolator("ok ${BAR:-def}") == "ok def"
assert defaults_interpolator("ok ${BAR-def}") == "ok "


def test_interpolate_with_missing_required_environment_variable(defaults_interpolator):
with pytest.raises(InvalidInterpolation):
defaults_interpolator('${missing?}')


def test_interpolate_with_existing_required_environment_variable(defaults_interpolator):
assert defaults_interpolator("ok ${FOO?}") == "ok first"