diff --git a/newsfragments/substitution-with-service-environment.feature b/newsfragments/substitution-with-service-environment.feature new file mode 100644 index 0000000..205d3f6 --- /dev/null +++ b/newsfragments/substitution-with-service-environment.feature @@ -0,0 +1 @@ +Add ability to substitute variables with the environment of the service. diff --git a/podman_compose.py b/podman_compose.py index a324e13..bfd9048 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -264,6 +264,16 @@ def rec_subs(value, subs_dict): do bash-like substitution in value and if list of dictionary do that recursively """ if is_dict(value): + if 'environment' in value and is_dict(value['environment']): + # Load service's environment variables + subs_dict = subs_dict.copy() + svc_envs = {k: v for k, v in value['environment'].items() if k not in subs_dict} + # we need to add `svc_envs` to the `subs_dict` so that it can evaluate the + # service environment that reference to another service environment. + subs_dict.update(svc_envs) + svc_envs = rec_subs(svc_envs, subs_dict) + subs_dict.update(svc_envs) + value = {k: rec_subs(v, subs_dict) for k, v in value.items()} elif is_str(value): @@ -1823,6 +1833,11 @@ def _parse_compose_file(self): "COMPOSE_FILE": pathsep.join(relative_files), "COMPOSE_PATH_SEPARATOR": pathsep, }) + + if args and 'env' in args and args.env: + env_vars = norm_as_dict(args.env) + self.environ.update(env_vars) + compose = {} # Iterate over files primitively to allow appending to files in-loop files_iter = iter(files) diff --git a/tests/integration/env-tests/container-compose.yml b/tests/integration/env-tests/container-compose.yml index ba39c3f..3a8b28e 100644 --- a/tests/integration/env-tests/container-compose.yml +++ b/tests/integration/env-tests/container-compose.yml @@ -1,9 +1,10 @@ -version: '3' +version: "3" services: env-test: image: busybox command: sh -c "export | grep ZZ" environment: - - ZZVAR1=myval1 - + ZZVAR1: myval1 + ZZVAR2: 2-$ZZVAR1 + ZZVAR3: 3-$ZZVAR2 diff --git a/tests/unit/test_rec_subs.py b/tests/unit/test_rec_subs.py new file mode 100644 index 0000000..4e83e17 --- /dev/null +++ b/tests/unit/test_rec_subs.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0 +# pylint: disable=protected-access + +import unittest + +from parameterized import parameterized + +from podman_compose import rec_subs + + +class TestRecSubs(unittest.TestCase): + substitutions = [ + # dict with environment variables + ( + "service's environment is low priority", + {"environment": {"v1": "low priority", "actual-v1": "$v1"}}, + {"environment": {"v1": "low priority", "actual-v1": "high priority"}}, + ), + ( + "service's environment can be used in other values", + {"environment": {"v100": "v1.0.0", "image": "abc:$v100"}}, + {"environment": {"v100": "v1.0.0", "image": "abc:v1.0.0"}}, + ), + ( + "Non-variable should not be substituted", + {"environment": {"non_var": "$$v1", "vx": "$non_var"}, "image": "abc:$non_var"}, + {"environment": {"non_var": "$v1", "vx": "$v1"}, "image": "abc:$v1"}, + ), + # list + ( + "Values in list are substituted", + ["$v1", "low priority"], + ["high priority", "low priority"], + ), + # str + ( + "Value with ${VARIABLE} format", + "${v1}", + "high priority", + ), + ( + "Value with ${VARIABLE:-default} format", + ["${v1:-default}", "${empty:-default}", "${not_exits:-default}"], + ["high priority", "default", "default"], + ), + ( + "Value with ${VARIABLE-default} format", + ["${v1-default}", "${empty-default}", "${not_exits-default}"], + ["high priority", "", "default"], + ), + ( + "Value $$ means $", + "$$v1", + "$v1", + ), + ] + + @parameterized.expand(substitutions) + def test_rec_subs(self, desc, input, expected): + sub_dict = {"v1": "high priority", "empty": ""} + result = rec_subs(input, sub_dict) + self.assertEqual(result, expected, msg=desc)