mirror of https://github.com/docker/compose.git
adds --no-interpolate to docker-compose config
Signed-off-by: Peter Nagy <pnagy@gratex.com>
This commit is contained in:
parent
b09d8802ed
commit
e34d329227
|
@ -37,7 +37,7 @@ SILENT_COMMANDS = set((
|
|||
))
|
||||
|
||||
|
||||
def project_from_options(project_dir, options):
|
||||
def project_from_options(project_dir, options, additional_options={}):
|
||||
override_dir = options.get('--project-directory')
|
||||
environment_file = options.get('--env-file')
|
||||
environment = Environment.from_env_file(override_dir or project_dir, environment_file)
|
||||
|
@ -57,6 +57,7 @@ def project_from_options(project_dir, options):
|
|||
environment=environment,
|
||||
override_dir=override_dir,
|
||||
compatibility=options.get('--compatibility'),
|
||||
interpolate=(not additional_options.get('--no-interpolate'))
|
||||
)
|
||||
|
||||
|
||||
|
@ -76,7 +77,7 @@ def set_parallel_limit(environment):
|
|||
parallel.GlobalLimit.set_global_limit(parallel_limit)
|
||||
|
||||
|
||||
def get_config_from_options(base_dir, options):
|
||||
def get_config_from_options(base_dir, options, additional_options={}):
|
||||
override_dir = options.get('--project-directory')
|
||||
environment_file = options.get('--env-file')
|
||||
environment = Environment.from_env_file(override_dir or base_dir, environment_file)
|
||||
|
@ -85,7 +86,8 @@ def get_config_from_options(base_dir, options):
|
|||
)
|
||||
return config.load(
|
||||
config.find(base_dir, config_path, environment, override_dir),
|
||||
options.get('--compatibility')
|
||||
options.get('--compatibility'),
|
||||
not additional_options.get('--no-interpolate')
|
||||
)
|
||||
|
||||
|
||||
|
@ -123,14 +125,14 @@ def get_client(environment, verbose=False, version=None, tls_config=None, host=N
|
|||
|
||||
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
||||
host=None, tls_config=None, environment=None, override_dir=None,
|
||||
compatibility=False):
|
||||
compatibility=False, interpolate=True):
|
||||
if not environment:
|
||||
environment = Environment.from_env_file(project_dir)
|
||||
config_details = config.find(project_dir, config_path, environment, override_dir)
|
||||
project_name = get_project_name(
|
||||
config_details.working_dir, project_name, environment
|
||||
)
|
||||
config_data = config.load(config_details, compatibility)
|
||||
config_data = config.load(config_details, compatibility, interpolate)
|
||||
|
||||
api_version = environment.get(
|
||||
'COMPOSE_API_VERSION',
|
||||
|
|
|
@ -331,6 +331,7 @@ class TopLevelCommand(object):
|
|||
|
||||
Options:
|
||||
--resolve-image-digests Pin image tags to digests.
|
||||
--no-interpolate Don't interpolate environment variables
|
||||
-q, --quiet Only validate the configuration, don't print
|
||||
anything.
|
||||
--services Print the service names, one per line.
|
||||
|
@ -340,11 +341,12 @@ class TopLevelCommand(object):
|
|||
or use the wildcard symbol to display all services
|
||||
"""
|
||||
|
||||
compose_config = get_config_from_options('.', self.toplevel_options)
|
||||
additional_options = {'--no-interpolate': options.get('--no-interpolate')}
|
||||
compose_config = get_config_from_options('.', self.toplevel_options, additional_options)
|
||||
image_digests = None
|
||||
|
||||
if options['--resolve-image-digests']:
|
||||
self.project = project_from_options('.', self.toplevel_options)
|
||||
self.project = project_from_options('.', self.toplevel_options, additional_options)
|
||||
with errors.handle_connection_errors(self.project.client):
|
||||
image_digests = image_digests_for_project(self.project)
|
||||
|
||||
|
@ -361,14 +363,14 @@ class TopLevelCommand(object):
|
|||
|
||||
if options['--hash'] is not None:
|
||||
h = options['--hash']
|
||||
self.project = project_from_options('.', self.toplevel_options)
|
||||
self.project = project_from_options('.', self.toplevel_options, additional_options)
|
||||
services = [svc for svc in options['--hash'].split(',')] if h != '*' else None
|
||||
with errors.handle_connection_errors(self.project.client):
|
||||
for service in self.project.get_services(services):
|
||||
print('{} {}'.format(service.name, service.config_hash))
|
||||
return
|
||||
|
||||
print(serialize_config(compose_config, image_digests))
|
||||
print(serialize_config(compose_config, image_digests, not options['--no-interpolate']))
|
||||
|
||||
def create(self, options):
|
||||
"""
|
||||
|
|
|
@ -373,7 +373,7 @@ def check_swarm_only_config(service_dicts, compatibility=False):
|
|||
check_swarm_only_key(service_dicts, 'configs')
|
||||
|
||||
|
||||
def load(config_details, compatibility=False):
|
||||
def load(config_details, compatibility=False, interpolate=True):
|
||||
"""Load the configuration from a working directory and a list of
|
||||
configuration files. Files are loaded in order, and merged on top
|
||||
of each other to create the final configuration.
|
||||
|
@ -383,7 +383,7 @@ def load(config_details, compatibility=False):
|
|||
validate_config_version(config_details.config_files)
|
||||
|
||||
processed_files = [
|
||||
process_config_file(config_file, config_details.environment)
|
||||
process_config_file(config_file, config_details.environment, interpolate=interpolate)
|
||||
for config_file in config_details.config_files
|
||||
]
|
||||
config_details = config_details._replace(config_files=processed_files)
|
||||
|
@ -505,7 +505,6 @@ def load_services(config_details, config_file, compatibility=False):
|
|||
|
||||
|
||||
def interpolate_config_section(config_file, config, section, environment):
|
||||
validate_config_section(config_file.filename, config, section)
|
||||
return interpolate_environment_variables(
|
||||
config_file.version,
|
||||
config,
|
||||
|
@ -514,38 +513,60 @@ def interpolate_config_section(config_file, config, section, environment):
|
|||
)
|
||||
|
||||
|
||||
def process_config_file(config_file, environment, service_name=None):
|
||||
services = interpolate_config_section(
|
||||
def process_config_section(config_file, config, section, environment, interpolate):
|
||||
validate_config_section(config_file.filename, config, section)
|
||||
if interpolate:
|
||||
return interpolate_environment_variables(
|
||||
config_file.version,
|
||||
config,
|
||||
section,
|
||||
environment
|
||||
)
|
||||
else:
|
||||
return config
|
||||
|
||||
|
||||
def process_config_file(config_file, environment, service_name=None, interpolate=True):
|
||||
services = process_config_section(
|
||||
config_file,
|
||||
config_file.get_service_dicts(),
|
||||
'service',
|
||||
environment)
|
||||
environment,
|
||||
interpolate,
|
||||
)
|
||||
|
||||
if config_file.version > V1:
|
||||
processed_config = dict(config_file.config)
|
||||
processed_config['services'] = services
|
||||
processed_config['volumes'] = interpolate_config_section(
|
||||
processed_config['volumes'] = process_config_section(
|
||||
config_file,
|
||||
config_file.get_volumes(),
|
||||
'volume',
|
||||
environment)
|
||||
processed_config['networks'] = interpolate_config_section(
|
||||
environment,
|
||||
interpolate,
|
||||
)
|
||||
processed_config['networks'] = process_config_section(
|
||||
config_file,
|
||||
config_file.get_networks(),
|
||||
'network',
|
||||
environment)
|
||||
environment,
|
||||
interpolate,
|
||||
)
|
||||
if config_file.version >= const.COMPOSEFILE_V3_1:
|
||||
processed_config['secrets'] = interpolate_config_section(
|
||||
processed_config['secrets'] = process_config_section(
|
||||
config_file,
|
||||
config_file.get_secrets(),
|
||||
'secret',
|
||||
environment)
|
||||
environment,
|
||||
interpolate,
|
||||
)
|
||||
if config_file.version >= const.COMPOSEFILE_V3_3:
|
||||
processed_config['configs'] = interpolate_config_section(
|
||||
processed_config['configs'] = process_config_section(
|
||||
config_file,
|
||||
config_file.get_configs(),
|
||||
'config',
|
||||
environment
|
||||
environment,
|
||||
interpolate,
|
||||
)
|
||||
else:
|
||||
processed_config = services
|
||||
|
|
|
@ -24,14 +24,12 @@ def serialize_dict_type(dumper, data):
|
|||
|
||||
|
||||
def serialize_string(dumper, data):
|
||||
""" Ensure boolean-like strings are quoted in the output and escape $ characters """
|
||||
""" Ensure boolean-like strings are quoted in the output """
|
||||
representer = dumper.represent_str if six.PY3 else dumper.represent_unicode
|
||||
|
||||
if isinstance(data, six.binary_type):
|
||||
data = data.decode('utf-8')
|
||||
|
||||
data = data.replace('$', '$$')
|
||||
|
||||
if data.lower() in ('y', 'n', 'yes', 'no', 'on', 'off', 'true', 'false'):
|
||||
# Empirically only y/n appears to be an issue, but this might change
|
||||
# depending on which PyYaml version is being used. Err on safe side.
|
||||
|
@ -39,6 +37,12 @@ def serialize_string(dumper, data):
|
|||
return representer(data)
|
||||
|
||||
|
||||
def serialize_string_escape_dollar(dumper, data):
|
||||
""" Ensure boolean-like strings are quoted in the output and escape $ characters """
|
||||
data = data.replace('$', '$$')
|
||||
return serialize_string(dumper, data)
|
||||
|
||||
|
||||
yaml.SafeDumper.add_representer(types.MountSpec, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(types.VolumeFromSpec, serialize_config_type)
|
||||
yaml.SafeDumper.add_representer(types.VolumeSpec, serialize_config_type)
|
||||
|
@ -46,8 +50,6 @@ yaml.SafeDumper.add_representer(types.SecurityOpt, serialize_config_type)
|
|||
yaml.SafeDumper.add_representer(types.ServiceSecret, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(types.ServiceConfig, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(types.ServicePort, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(str, serialize_string)
|
||||
yaml.SafeDumper.add_representer(six.text_type, serialize_string)
|
||||
|
||||
|
||||
def denormalize_config(config, image_digests=None):
|
||||
|
@ -93,7 +95,13 @@ def v3_introduced_name_key(key):
|
|||
return V3_5
|
||||
|
||||
|
||||
def serialize_config(config, image_digests=None):
|
||||
def serialize_config(config, image_digests=None, escape_dollar=True):
|
||||
if escape_dollar:
|
||||
yaml.SafeDumper.add_representer(str, serialize_string_escape_dollar)
|
||||
yaml.SafeDumper.add_representer(six.text_type, serialize_string_escape_dollar)
|
||||
else:
|
||||
yaml.SafeDumper.add_representer(str, serialize_string)
|
||||
yaml.SafeDumper.add_representer(six.text_type, serialize_string)
|
||||
return yaml.safe_dump(
|
||||
denormalize_config(config, image_digests),
|
||||
default_flow_style=False,
|
||||
|
|
|
@ -613,6 +613,25 @@ class ConfigTest(unittest.TestCase):
|
|||
excinfo.exconly()
|
||||
)
|
||||
|
||||
def test_config_integer_service_name_raise_validation_error_v2_when_no_interpolate(self):
|
||||
with pytest.raises(ConfigurationError) as excinfo:
|
||||
config.load(
|
||||
build_config_details(
|
||||
{
|
||||
'version': '2',
|
||||
'services': {1: {'image': 'busybox'}}
|
||||
},
|
||||
'working_dir',
|
||||
'filename.yml'
|
||||
),
|
||||
interpolate=False
|
||||
)
|
||||
|
||||
assert (
|
||||
"In file 'filename.yml', the service name 1 must be a quoted string, i.e. '1'." in
|
||||
excinfo.exconly()
|
||||
)
|
||||
|
||||
def test_config_integer_service_property_raise_validation_error(self):
|
||||
with pytest.raises(ConfigurationError) as excinfo:
|
||||
config.load(
|
||||
|
@ -5328,6 +5347,28 @@ class SerializeTest(unittest.TestCase):
|
|||
assert serialized_service['command'] == 'echo $$FOO'
|
||||
assert serialized_service['entrypoint'][0] == '$$SHELL'
|
||||
|
||||
def test_serialize_escape_dont_interpolate(self):
|
||||
cfg = {
|
||||
'version': '2.2',
|
||||
'services': {
|
||||
'web': {
|
||||
'image': 'busybox',
|
||||
'command': 'echo $FOO',
|
||||
'environment': {
|
||||
'CURRENCY': '$'
|
||||
},
|
||||
'entrypoint': ['$SHELL', '-c'],
|
||||
}
|
||||
}
|
||||
}
|
||||
config_dict = config.load(build_config_details(cfg), interpolate=False)
|
||||
|
||||
serialized_config = yaml.load(serialize_config(config_dict, escape_dollar=False))
|
||||
serialized_service = serialized_config['services']['web']
|
||||
assert serialized_service['environment']['CURRENCY'] == '$'
|
||||
assert serialized_service['command'] == 'echo $FOO'
|
||||
assert serialized_service['entrypoint'][0] == '$SHELL'
|
||||
|
||||
def test_serialize_unicode_values(self):
|
||||
cfg = {
|
||||
'version': '2.3',
|
||||
|
|
Loading…
Reference in New Issue