From f17e7268b0a7fd287bd8be3af61f9363222d2e07 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Thu, 3 Oct 2019 15:46:49 +0200 Subject: [PATCH 1/3] Properly escape values coming from env_files, fixes #6871 Signed-off-by: Florian Apolloner --- compose/config/config.py | 16 +++++++++------- compose/config/environment.py | 3 ++- tests/fixtures/env/three.env | 2 ++ tests/unit/config/config_test.py | 6 +++++- 4 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/env/three.env diff --git a/compose/config/config.py b/compose/config/config.py index 84933e9c9..3bd49a0f7 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -408,7 +408,7 @@ def load(config_details, compatibility=False, interpolate=True): configs = load_mapping( config_details.config_files, 'get_configs', 'Config', config_details.working_dir ) - service_dicts = load_services(config_details, main_file, compatibility) + service_dicts = load_services(config_details, main_file, compatibility, interpolate=interpolate) if main_file.version != V1: for service_dict in service_dicts: @@ -460,7 +460,7 @@ def validate_external(entity_type, name, config, version): entity_type, name, ', '.join(k for k in config if k != 'external'))) -def load_services(config_details, config_file, compatibility=False): +def load_services(config_details, config_file, compatibility=False, interpolate=True): def build_service(service_name, service_dict, service_names): service_config = ServiceConfig.with_abs_paths( config_details.working_dir, @@ -479,7 +479,8 @@ def load_services(config_details, config_file, compatibility=False): service_names, config_file.version, config_details.environment, - compatibility + compatibility, + interpolate ) return service_dict @@ -679,13 +680,13 @@ class ServiceExtendsResolver(object): return filename -def resolve_environment(service_dict, environment=None): +def resolve_environment(service_dict, environment=None, interpolate=True): """Unpack any environment variables from an env_file, if set. Interpolate environment values if set. """ env = {} for env_file in service_dict.get('env_file', []): - env.update(env_vars_from_file(env_file)) + env.update(env_vars_from_file(env_file, interpolate)) env.update(parse_environment(service_dict.get('environment'))) return dict(resolve_env_var(k, v, environment) for k, v in six.iteritems(env)) @@ -881,11 +882,12 @@ def finalize_service_volumes(service_dict, environment): return service_dict -def finalize_service(service_config, service_names, version, environment, compatibility): +def finalize_service(service_config, service_names, version, environment, compatibility, + interpolate=True): service_dict = dict(service_config.config) if 'environment' in service_dict or 'env_file' in service_dict: - service_dict['environment'] = resolve_environment(service_dict, environment) + service_dict['environment'] = resolve_environment(service_dict, environment, interpolate) service_dict.pop('env_file', None) if 'volumes_from' in service_dict: diff --git a/compose/config/environment.py b/compose/config/environment.py index 6afbfc972..292029eb5 100644 --- a/compose/config/environment.py +++ b/compose/config/environment.py @@ -30,7 +30,7 @@ def split_env(env): return key, value -def env_vars_from_file(filename): +def env_vars_from_file(filename, interpolate=True): """ Read in a line delimited file of environment variables. """ @@ -39,6 +39,7 @@ def env_vars_from_file(filename): elif not os.path.isfile(filename): raise EnvFileNotFound("{} is not a file.".format(filename)) + # TODO: now we should do something with interpolate here, but what? return dotenv.dotenv_values(dotenv_path=filename, encoding='utf-8-sig') diff --git a/tests/fixtures/env/three.env b/tests/fixtures/env/three.env new file mode 100644 index 000000000..c2da74f19 --- /dev/null +++ b/tests/fixtures/env/three.env @@ -0,0 +1,2 @@ +FOO=NO $ENV VAR +DOO=NO ${ENV} VAR diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index dc346df95..933f659f8 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -5420,15 +5420,19 @@ class SerializeTest(unittest.TestCase): 'environment': { 'CURRENCY': '$' }, + 'env_file': ['tests/fixtures/env/three.env'], 'entrypoint': ['$SHELL', '-c'], } } } - config_dict = config.load(build_config_details(cfg), interpolate=False) + config_dict = config.load(build_config_details(cfg, working_dir='.'), interpolate=False) serialized_config = yaml.safe_load(serialize_config(config_dict, escape_dollar=False)) serialized_service = serialized_config['services']['web'] assert serialized_service['environment']['CURRENCY'] == '$' + # Values coming from env_files are not allowed to have variables + assert serialized_service['environment']['FOO'] == 'NO $$ENV VAR' + assert serialized_service['environment']['DOO'] == 'NO $${ENV} VAR' assert serialized_service['command'] == 'echo $FOO' assert serialized_service['entrypoint'][0] == '$SHELL' From edf27e486a1f556a74c915f9c1915c0b4466970e Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 29 Jan 2020 14:29:32 +0100 Subject: [PATCH 2/3] Pass the interpolation value to python-dotenv Signed-off-by: Ulysses Souza --- compose/config/environment.py | 6 ++++-- requirements.txt | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compose/config/environment.py b/compose/config/environment.py index 292029eb5..d8810ebb6 100644 --- a/compose/config/environment.py +++ b/compose/config/environment.py @@ -39,8 +39,10 @@ def env_vars_from_file(filename, interpolate=True): elif not os.path.isfile(filename): raise EnvFileNotFound("{} is not a file.".format(filename)) - # TODO: now we should do something with interpolate here, but what? - return dotenv.dotenv_values(dotenv_path=filename, encoding='utf-8-sig') + env = dotenv.dotenv_values(dotenv_path=filename, encoding='utf-8-sig', interpolate=interpolate) + for k, v in env.items(): + env[k] = v if interpolate else v.replace('$', '$$') + return env class Environment(dict): diff --git a/requirements.txt b/requirements.txt index ee57c26b5..8b1eca573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ dockerpty==0.4.1 docopt==0.6.2 enum34==1.1.6; python_version < '3.4' functools32==3.2.3.post2; python_version < '3.2' +git+git://github.com/ulyssessouza/python-dotenv.git@no-interpolate#egg=python-dotenv idna==2.8 ipaddress==1.0.23 jsonschema==3.2.0 @@ -17,7 +18,7 @@ paramiko==2.7.1 pypiwin32==219; sys_platform == 'win32' and python_version < '3.6' pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6' PySocks==1.7.1 -python-dotenv==0.10.5 +#python-dotenv==0.10.5 PyYAML==5.3 requests==2.22.0 six==1.12.0 From 5fe0858450303f701175dbc879d68fc558adda2d Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Thu, 6 Feb 2020 09:44:37 +0100 Subject: [PATCH 3/3] Updated requirements.txt back to the released python-dotenv 0.11.0. Signed-off-by: Florian Apolloner --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8b1eca573..92f5d104d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,6 @@ dockerpty==0.4.1 docopt==0.6.2 enum34==1.1.6; python_version < '3.4' functools32==3.2.3.post2; python_version < '3.2' -git+git://github.com/ulyssessouza/python-dotenv.git@no-interpolate#egg=python-dotenv idna==2.8 ipaddress==1.0.23 jsonschema==3.2.0 @@ -18,7 +17,7 @@ paramiko==2.7.1 pypiwin32==219; sys_platform == 'win32' and python_version < '3.6' pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6' PySocks==1.7.1 -#python-dotenv==0.10.5 +python-dotenv==0.11.0 PyYAML==5.3 requests==2.22.0 six==1.12.0