diff --git a/podman_compose.py b/podman_compose.py index a0ea0587..57579845 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -260,8 +260,8 @@ def fix_mount_dict( (?:{ (?P[_a-zA-Z][_a-zA-Z0-9]*) (?:(?P:)?(?: - (?:-(?P[^}]*)) | - (?:\?(?P[^}]*)) + (?:-(?P.*)) | + (?:\?(?P.*)) ))? }) ) @@ -305,8 +305,8 @@ def convert(m: re.Match) -> str: if value is not None: return str(value) if m.group("err") is not None: - raise RuntimeError(m.group("err")) - return m.group("default") or "" + raise RuntimeError(rec_subs(m.group("err"), subs_dict)) + return rec_subs(m.group("default") or "", subs_dict) value = var_re.sub(convert, value) elif hasattr(value, "__iter__"): diff --git a/tests/integration/interpolation/.env b/tests/integration/interpolation/.env index 2728dd9e..15c3ad8d 100644 --- a/tests/integration/interpolation/.env +++ b/tests/integration/interpolation/.env @@ -1,2 +1,3 @@ DOT_ENV_VARIABLE=This value is from the .env file TEST_LABELS=TEST +DOT_ENV_VARIABLE_INTERPOLATION=Dotenv with default ${DEFAULT} diff --git a/tests/integration/interpolation/docker-compose-nested.yml b/tests/integration/interpolation/docker-compose-nested.yml new file mode 100644 index 00000000..c95a5901 --- /dev/null +++ b/tests/integration/interpolation/docker-compose-nested.yml @@ -0,0 +1,19 @@ +version: "3.7" +services: + variables: + image: busybox + command: ["/bin/busybox", "sh", "-c", "export | grep EXAMPLE"] + environment: + EXAMPLE_COLON_DASH_DEFAULT_WITH_INTERPOLATION: ${EMPTY:-My default is ${DEFAULT}} + EXAMPLE_DASH_DEFAULT_WITH_INTERPOLATION: ${NOT_SET-My default is ${DEFAULT}} + EXAMPLE_DOTENV_INTERPOLATION: ${DOT_ENV_VARIABLE_INTERPOLATION} + labels_test: + image: busybox + labels: + - "${EMPTY:-empty_${TEST_LABELS}}=test_labels" + - "${NOT_SET-notset_${TEST_LABELS}}=test_labels" + - test.${EMPTY:-empty_${TEST_LABELS}}=${EMPTY:-empty_${TEST_LABELS}} + - test.${NOT_SET-notset_${TEST_LABELS}}=${NOT_SET-notset_${TEST_LABELS}} + - "${EMPTY:-empty_${TEST_LABELS}}.test2=test2(`${EMPTY:-empty_${TEST_LABELS}}`)" + - "${NOT_SET-notset_${TEST_LABELS}}.test2=test2(`${NOT_SET-notset_${TEST_LABELS}}`)" + diff --git a/tests/integration/interpolation/test_podman_compose_interpolation.py b/tests/integration/interpolation/test_podman_compose_interpolation.py index b9ae0c9c..3586a645 100644 --- a/tests/integration/interpolation/test_podman_compose_interpolation.py +++ b/tests/integration/interpolation/test_podman_compose_interpolation.py @@ -9,8 +9,8 @@ from tests.integration.test_utils import test_path -def compose_yaml_path() -> str: - return os.path.join(os.path.join(test_path(), "interpolation"), "docker-compose.yml") +def compose_yaml_path(file: str) -> str: + return os.path.join(os.path.join(test_path(), "interpolation"), file) class TestComposeInterpolation(unittest.TestCase, RunSubprocessMixin): @@ -21,13 +21,13 @@ def test_interpolation(self) -> None: "EXAMPLE_VARIABLE_USER=test_user", podman_compose_path(), "-f", - compose_yaml_path(), + compose_yaml_path("docker-compose.yml"), "up", ]) output, _ = self.run_subprocess_assert_returncode([ podman_compose_path(), "-f", - compose_yaml_path(), + compose_yaml_path("docker-compose.yml"), "logs", ]) self.assertIn("EXAMPLE_VARIABLE='Host user: test_user'", str(output)) @@ -53,6 +53,53 @@ def test_interpolation(self) -> None: self.run_subprocess_assert_returncode([ podman_compose_path(), "-f", - compose_yaml_path(), + compose_yaml_path("docker-compose.yml"), + "down", + ]) + + def test_nested_interpolation(self) -> None: + try: + self.run_subprocess_assert_returncode([ + "env", + "DEFAULT=nested", + "TEST_LABELS=foo", + podman_compose_path(), + "-f", + compose_yaml_path("docker-compose-nested.yml"), + "up", + ]) + output, _ = self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path("docker-compose-nested.yml"), + "logs", + ]) + self.assertIn( + "EXAMPLE_COLON_DASH_DEFAULT_WITH_INTERPOLATION='My default is nested'", str(output) + ) + self.assertIn( + "EXAMPLE_DASH_DEFAULT_WITH_INTERPOLATION='My default is nested'", str(output) + ) + self.assertIn("EXAMPLE_DOTENV_INTERPOLATION='Dotenv with default nested'", str(output)) + + output, _ = self.run_subprocess_assert_returncode([ + "podman", + "inspect", + "interpolation_labels_test_1", + ]) + inspect_output = json.loads(output) + labels_dict = inspect_output[0].get("Config", {}).get("Labels", {}) + self.assertIn(('empty_foo', 'test_labels'), labels_dict.items()) + self.assertIn(('notset_foo', 'test_labels'), labels_dict.items()) + self.assertIn(('test.empty_foo', 'empty_foo'), labels_dict.items()) + self.assertIn(('test.notset_foo', 'notset_foo'), labels_dict.items()) + self.assertIn(('empty_foo.test2', 'test2(`empty_foo`)'), labels_dict.items()) + self.assertIn(('notset_foo.test2', 'test2(`notset_foo`)'), labels_dict.items()) + + finally: + self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path("docker-compose-nested.yml"), "down", ]) diff --git a/tests/unit/test_rec_subs.py b/tests/unit/test_rec_subs.py index cd7342c4..6063cd13 100644 --- a/tests/unit/test_rec_subs.py +++ b/tests/unit/test_rec_subs.py @@ -9,6 +9,15 @@ from podman_compose import rec_subs +def from_pairs(pairs: list[list[str]]) -> tuple[list[str], list[str]]: + keys = [] + values = [] + for key, value in pairs: + keys.append(key) + values.append(value) + return keys, values + + class TestRecSubs(unittest.TestCase): substitutions = [ # dict with environment variables @@ -64,6 +73,38 @@ class TestRecSubs(unittest.TestCase): "$$v1", "$v1", ), + ( + "Nested interpolation", + *from_pairs([ + ["${v1:-${v1}}", "high priority"], + ["${v1:-${v1:-default}}", "high priority"], + ["${v1:-${v1-default}}", "high priority"], + ["${v1:-${empty}}", "high priority"], + ["${v1:-${empty:-default}}", "high priority"], + ["${v1:-${empty-default}}", "high priority"], + ["${v1:-${not_exits}}", "high priority"], + ["${v1:-${not_exits:-default}}", "high priority"], + ["${v1:-${not_exits-default}}", "high priority"], + ["${empty:-${v1}}", "high priority"], + ["${empty:-${v1:-default}}", "high priority"], + ["${empty:-${v1-default}}", "high priority"], + ["${empty:-${empty}}", ""], + ["${empty:-${empty:-default}}", "default"], + ["${empty:-${empty-default}}", ""], + ["${empty:-${not_exits}}", ""], + ["${empty:-${not_exits:-default}}", "default"], + ["${empty:-${not_exits-default}}", "default"], + ["${not_exits:-${v1}}", "high priority"], + ["${not_exits:-${v1:-default}}", "high priority"], + ["${not_exits:-${v1-default}}", "high priority"], + ["${not_exits:-${empty}}", ""], + ["${not_exits:-${empty:-default}}", "default"], + ["${not_exits:-${empty-default}}", ""], + ["${not_exits:-${not_exits}}", ""], + ["${not_exits:-${not_exits:-default}}", "default"], + ["${not_exits:-${not_exits-default}}", "default"], + ]), + ), ] @parameterized.expand(substitutions)