mirror of https://github.com/docker/compose.git
Support ${VAR:?err} syntax for mandatory variables
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
bcc13d7fae
commit
e400c05de0
|
@ -60,6 +60,15 @@ def interpolate_value(name, config_key, value, section, interpolator):
|
||||||
name=name,
|
name=name,
|
||||||
section=section,
|
section=section,
|
||||||
string=e.string))
|
string=e.string))
|
||||||
|
except UnsetRequiredSubstitution as e:
|
||||||
|
raise ConfigurationError(
|
||||||
|
'Missing mandatory value for "{config_key}" option in {section} "{name}": {err}'.format(
|
||||||
|
config_key=config_key,
|
||||||
|
name=name,
|
||||||
|
section=section,
|
||||||
|
err=e.err
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def recursive_interpolate(obj, interpolator, config_path):
|
def recursive_interpolate(obj, interpolator, config_path):
|
||||||
|
@ -79,21 +88,54 @@ def recursive_interpolate(obj, interpolator, config_path):
|
||||||
|
|
||||||
|
|
||||||
class TemplateWithDefaults(Template):
|
class TemplateWithDefaults(Template):
|
||||||
idpattern = r'[_a-z][_a-z0-9]*(?::?-[^}]*)?'
|
pattern = r"""
|
||||||
|
%(delim)s(?:
|
||||||
|
(?P<escaped>%(delim)s) |
|
||||||
|
(?P<named>%(id)s) |
|
||||||
|
{(?P<braced>%(bid)s)} |
|
||||||
|
(?P<invalid>)
|
||||||
|
)
|
||||||
|
""" % {
|
||||||
|
'delim': re.escape('$'),
|
||||||
|
'id': r'[_a-z][_a-z0-9]*',
|
||||||
|
'bid': r'[_a-z][_a-z0-9]*(?:(?P<sep>:?[-?])[^}]*)?',
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_braced_group(braced, sep, mapping):
|
||||||
|
if ':-' == sep:
|
||||||
|
var, _, default = braced.partition(':-')
|
||||||
|
return mapping.get(var) or default
|
||||||
|
elif '-' == sep:
|
||||||
|
var, _, default = braced.partition('-')
|
||||||
|
return mapping.get(var, default)
|
||||||
|
|
||||||
|
elif ':?' == sep:
|
||||||
|
var, _, err = braced.partition(':?')
|
||||||
|
result = mapping.get(var)
|
||||||
|
if not result:
|
||||||
|
raise UnsetRequiredSubstitution(err)
|
||||||
|
return result
|
||||||
|
elif '?' == sep:
|
||||||
|
var, _, err = braced.partition('?')
|
||||||
|
if var in mapping:
|
||||||
|
return mapping.get(var)
|
||||||
|
raise UnsetRequiredSubstitution(err)
|
||||||
|
|
||||||
# Modified from python2.7/string.py
|
# Modified from python2.7/string.py
|
||||||
def substitute(self, mapping):
|
def substitute(self, mapping):
|
||||||
# Helper function for .sub()
|
# Helper function for .sub()
|
||||||
|
|
||||||
def convert(mo):
|
def convert(mo):
|
||||||
# Check the most common path first.
|
|
||||||
named = mo.group('named') or mo.group('braced')
|
named = mo.group('named') or mo.group('braced')
|
||||||
|
braced = mo.group('braced')
|
||||||
|
if braced is not None:
|
||||||
|
sep = mo.group('sep')
|
||||||
|
result = self.process_braced_group(braced, sep, mapping)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
if named is not None:
|
if named is not None:
|
||||||
if ':-' in named:
|
|
||||||
var, _, default = named.partition(':-')
|
|
||||||
return mapping.get(var) or default
|
|
||||||
if '-' in named:
|
|
||||||
var, _, default = named.partition('-')
|
|
||||||
return mapping.get(var, default)
|
|
||||||
val = mapping[named]
|
val = mapping[named]
|
||||||
return '%s' % (val,)
|
return '%s' % (val,)
|
||||||
if mo.group('escaped') is not None:
|
if mo.group('escaped') is not None:
|
||||||
|
@ -110,6 +152,11 @@ class InvalidInterpolation(Exception):
|
||||||
self.string = string
|
self.string = string
|
||||||
|
|
||||||
|
|
||||||
|
class UnsetRequiredSubstitution(Exception):
|
||||||
|
def __init__(self, custom_err_msg):
|
||||||
|
self.err = custom_err_msg
|
||||||
|
|
||||||
|
|
||||||
PATH_JOKER = '[^.]+'
|
PATH_JOKER = '[^.]+'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ class ConsoleWarningFormatterTestCase(unittest.TestCase):
|
||||||
def test_format_unicode_info(self):
|
def test_format_unicode_info(self):
|
||||||
message = b'\xec\xa0\x95\xec\x88\x98\xec\xa0\x95'
|
message = b'\xec\xa0\x95\xec\x88\x98\xec\xa0\x95'
|
||||||
output = self.formatter.format(make_log_record(logging.INFO, message))
|
output = self.formatter.format(make_log_record(logging.INFO, message))
|
||||||
print(output)
|
|
||||||
assert output == message.decode('utf-8')
|
assert output == message.decode('utf-8')
|
||||||
|
|
||||||
def test_format_unicode_warn(self):
|
def test_format_unicode_warn(self):
|
||||||
|
|
|
@ -9,6 +9,7 @@ from compose.config.interpolation import interpolate_environment_variables
|
||||||
from compose.config.interpolation import Interpolator
|
from compose.config.interpolation import Interpolator
|
||||||
from compose.config.interpolation import InvalidInterpolation
|
from compose.config.interpolation import InvalidInterpolation
|
||||||
from compose.config.interpolation import TemplateWithDefaults
|
from compose.config.interpolation import TemplateWithDefaults
|
||||||
|
from compose.config.interpolation import UnsetRequiredSubstitution
|
||||||
from compose.const import COMPOSEFILE_V2_0 as V2_0
|
from compose.const import COMPOSEFILE_V2_0 as V2_0
|
||||||
from compose.const import COMPOSEFILE_V2_3 as V2_3
|
from compose.const import COMPOSEFILE_V2_3 as V2_3
|
||||||
from compose.const import COMPOSEFILE_V3_4 as V3_4
|
from compose.const import COMPOSEFILE_V3_4 as V3_4
|
||||||
|
@ -357,9 +358,46 @@ def test_interpolate_with_value(defaults_interpolator):
|
||||||
def test_interpolate_missing_with_default(defaults_interpolator):
|
def test_interpolate_missing_with_default(defaults_interpolator):
|
||||||
assert defaults_interpolator("ok ${missing:-def}") == "ok def"
|
assert defaults_interpolator("ok ${missing:-def}") == "ok def"
|
||||||
assert defaults_interpolator("ok ${missing-def}") == "ok def"
|
assert defaults_interpolator("ok ${missing-def}") == "ok def"
|
||||||
assert defaults_interpolator("ok ${BAR:-/non:-alphanumeric}") == "ok /non:-alphanumeric"
|
|
||||||
|
|
||||||
|
|
||||||
def test_interpolate_with_empty_and_default_value(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"
|
||||||
assert defaults_interpolator("ok ${BAR-def}") == "ok "
|
assert defaults_interpolator("ok ${BAR-def}") == "ok "
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_mandatory_values(defaults_interpolator):
|
||||||
|
assert defaults_interpolator("ok ${FOO:?bar}") == "ok first"
|
||||||
|
assert defaults_interpolator("ok ${FOO?bar}") == "ok first"
|
||||||
|
assert defaults_interpolator("ok ${BAR?bar}") == "ok "
|
||||||
|
|
||||||
|
with pytest.raises(UnsetRequiredSubstitution) as e:
|
||||||
|
defaults_interpolator("not ok ${BAR:?high bar}")
|
||||||
|
assert e.value.err == 'high bar'
|
||||||
|
|
||||||
|
with pytest.raises(UnsetRequiredSubstitution) as e:
|
||||||
|
defaults_interpolator("not ok ${BAZ?dropped the bazz}")
|
||||||
|
assert e.value.err == 'dropped the bazz'
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_mandatory_no_err_msg(defaults_interpolator):
|
||||||
|
with pytest.raises(UnsetRequiredSubstitution) as e:
|
||||||
|
defaults_interpolator("not ok ${BAZ?}")
|
||||||
|
|
||||||
|
assert e.value.err == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_mixed_separators(defaults_interpolator):
|
||||||
|
assert defaults_interpolator("ok ${BAR:-/non:-alphanumeric}") == "ok /non:-alphanumeric"
|
||||||
|
assert defaults_interpolator("ok ${BAR:-:?wwegegr??:?}") == "ok :?wwegegr??:?"
|
||||||
|
assert defaults_interpolator("ok ${BAR-:-hello}") == 'ok '
|
||||||
|
|
||||||
|
with pytest.raises(UnsetRequiredSubstitution) as e:
|
||||||
|
defaults_interpolator("not ok ${BAR:?xazz:-redf}")
|
||||||
|
assert e.value.err == 'xazz:-redf'
|
||||||
|
|
||||||
|
assert defaults_interpolator("ok ${BAR?...:?bar}") == "ok "
|
||||||
|
|
||||||
|
|
||||||
|
def test_unbraced_separators(defaults_interpolator):
|
||||||
|
assert defaults_interpolator("ok $FOO:-bar") == "ok first:-bar"
|
||||||
|
assert defaults_interpolator("ok $BAZ?error") == "ok ?error"
|
||||||
|
|
Loading…
Reference in New Issue