From 170411de8be48960679361c6ab8e6b7256902904 Mon Sep 17 00:00:00 2001 From: Ruben Jenster Date: Wed, 12 Mar 2025 16:20:58 +0100 Subject: [PATCH 1/4] test/integration: Test COMPOSE_PROJECT_NAME interpolation Refs #1073 Signed-off-by: Ruben Jenster --- .../env-tests/container-compose.yml | 8 +++++ .../env-tests/test_podman_compose_env.py | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tests/integration/env-tests/container-compose.yml b/tests/integration/env-tests/container-compose.yml index 3a8b28eb..4498f09d 100644 --- a/tests/integration/env-tests/container-compose.yml +++ b/tests/integration/env-tests/container-compose.yml @@ -1,5 +1,7 @@ version: "3" +name: my-project-name + services: env-test: image: busybox @@ -8,3 +10,9 @@ services: ZZVAR1: myval1 ZZVAR2: 2-$ZZVAR1 ZZVAR3: 3-$ZZVAR2 + + project-name-test: + image: busybox + command: sh -c "echo $$PNAME" + environment: + PNAME: ${COMPOSE_PROJECT_NAME} diff --git a/tests/integration/env-tests/test_podman_compose_env.py b/tests/integration/env-tests/test_podman_compose_env.py index 351644f3..3d50a077 100644 --- a/tests/integration/env-tests/test_podman_compose_env.py +++ b/tests/integration/env-tests/test_podman_compose_env.py @@ -36,3 +36,34 @@ def test_env(self): compose_yaml_path(), "down", ]) + + """ + Tests interpolation of COMPOSE_PROJECT_NAME in the podman-compose config, + which is different from external environment variables because COMPOSE_PROJECT_NAME + is a predefined environment variable generated from the `name` value in the top-level + of the compose.yaml. + + See also + - https://docs.docker.com/reference/compose-file/interpolation/ + - https://docs.docker.com/reference/compose-file/version-and-name/#name-top-level-element + - https://docs.docker.com/compose/how-tos/environment-variables/envvars/ + - https://github.com/compose-spec/compose-spec/blob/main/04-version-and-name.md + """ + + def test_project_name(self): + try: + output, _ = self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path(), + "run", + "project-name-test", + ]) + self.assertIn("my-project-name", str(output)) + finally: + self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path(), + "down", + ]) From 98b9bb9f8ed18315f87bd5106ae29ca70b8866c6 Mon Sep 17 00:00:00 2001 From: Ruben Jenster Date: Thu, 20 Mar 2025 12:06:19 +0100 Subject: [PATCH 2/4] Fix interpolation for COMPOSE_PROJECT_NAME Fixes #1073 Signed-off-by: Ruben Jenster --- .../1165-PROJECT_NAME-interpolation.bugfix | 1 + podman_compose.py | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 newsfragments/1165-PROJECT_NAME-interpolation.bugfix diff --git a/newsfragments/1165-PROJECT_NAME-interpolation.bugfix b/newsfragments/1165-PROJECT_NAME-interpolation.bugfix new file mode 100644 index 00000000..e4200e0a --- /dev/null +++ b/newsfragments/1165-PROJECT_NAME-interpolation.bugfix @@ -0,0 +1 @@ +- Fixed interpolation of the environment variable **COMPOSE_PROJECT_NAME** when it is set from the top-level **name** value within the Compose file. diff --git a/podman_compose.py b/podman_compose.py index df1f9ee9..379d9dcc 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -1980,6 +1980,20 @@ def _parse_compose_file(self): sys.exit(1) content = normalize(content) # log(filename, json.dumps(content, indent = 2)) + + if not project_name: + project_name = content.get("name") + if project_name is None: + # More strict then actually needed for simplicity: + # podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]* + project_name = self.environ.get("COMPOSE_PROJECT_NAME", dir_basename.lower()) + project_name = norm_re.sub("", project_name) + if not project_name: + raise RuntimeError(f"Project name [{dir_basename}] normalized to empty") + + self.project_name = project_name + self.environ.update({"COMPOSE_PROJECT_NAME": self.project_name}) + content = rec_subs(content, self.environ) if isinstance(services := content.get('services'), dict): for service in services.values(): @@ -2012,19 +2026,6 @@ def _parse_compose_file(self): log.debug(" ** merged:\n%s", json.dumps(compose, indent=2)) # ver = compose.get('version') - if not project_name: - project_name = compose.get("name") - if project_name is None: - # More strict then actually needed for simplicity: - # podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]* - project_name = self.environ.get("COMPOSE_PROJECT_NAME", dir_basename.lower()) - project_name = norm_re.sub("", project_name) - if not project_name: - raise RuntimeError(f"Project name [{dir_basename}] normalized to empty") - - self.project_name = project_name - self.environ.update({"COMPOSE_PROJECT_NAME": self.project_name}) - services = compose.get("services") if services is None: services = {} From 1aa750bacfa0fa629642713f956b144d3d41ba21 Mon Sep 17 00:00:00 2001 From: Ruben Jenster Date: Wed, 12 Mar 2025 19:10:05 +0100 Subject: [PATCH 3/4] integration/tests: Test project name override with COMPOSE_PROJECT_NAME env variable Signed-off-by: Ruben Jenster --- .../env-tests/test_podman_compose_env.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/integration/env-tests/test_podman_compose_env.py b/tests/integration/env-tests/test_podman_compose_env.py index 3d50a077..f1c35cf4 100644 --- a/tests/integration/env-tests/test_podman_compose_env.py +++ b/tests/integration/env-tests/test_podman_compose_env.py @@ -67,3 +67,23 @@ def test_project_name(self): compose_yaml_path(), "down", ]) + + def test_project_name_override(self): + try: + output, _ = self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path(), + "run", + "-e", + "COMPOSE_PROJECT_NAME=project-name-override", + "project-name-test", + ]) + self.assertIn("project-name-override", str(output)) + finally: + self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path(), + "down", + ]) From 65b455f0811189859baedb4f69d36a55b388274f Mon Sep 17 00:00:00 2001 From: Ruben Jenster Date: Wed, 12 Mar 2025 19:04:38 +0100 Subject: [PATCH 4/4] Fix project name evaluation order The COMPOSE_PROJECT_NAME environment variable must override the top-level name: attribute in the Compose file. The precedence order is defined in the docker compose documentation https://docs.docker.com/compose/how-tos/project-name/#set-a-project-name Signed-off-by: Ruben Jenster --- .../1165-project-name-evaluation-order.bugfix | 1 + podman_compose.py | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 newsfragments/1165-project-name-evaluation-order.bugfix diff --git a/newsfragments/1165-project-name-evaluation-order.bugfix b/newsfragments/1165-project-name-evaluation-order.bugfix new file mode 100644 index 00000000..d8834e17 --- /dev/null +++ b/newsfragments/1165-project-name-evaluation-order.bugfix @@ -0,0 +1 @@ +- Fixed project name evaluation order to match the order defined in the [compose spec](https://docs.docker.com/compose/how-tos/project-name/#set-a-project-name). diff --git a/podman_compose.py b/podman_compose.py index 379d9dcc..62ec408e 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -1981,15 +1981,20 @@ def _parse_compose_file(self): content = normalize(content) # log(filename, json.dumps(content, indent = 2)) + # See also https://docs.docker.com/compose/how-tos/project-name/#set-a-project-name + # **project_name** is initialized to the argument of the `-p` command line flag. if not project_name: - project_name = content.get("name") - if project_name is None: - # More strict then actually needed for simplicity: - # podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]* - project_name = self.environ.get("COMPOSE_PROJECT_NAME", dir_basename.lower()) - project_name = norm_re.sub("", project_name) - if not project_name: - raise RuntimeError(f"Project name [{dir_basename}] normalized to empty") + project_name = self.environ.get("COMPOSE_PROJECT_NAME") + if not project_name: + project_name = content.get("name") + if not project_name: + project_name = dir_basename.lower() + # More strict then actually needed for simplicity: + # podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]* + project_name_normalized = norm_re.sub("", project_name) + if not project_name_normalized: + raise RuntimeError(f"Project name [{project_name}] normalized to empty") + project_name = project_name_normalized self.project_name = project_name self.environ.update({"COMPOSE_PROJECT_NAME": self.project_name})