From 8f5522716764b8bb1f283d1776c73ffa2826c2c5 Mon Sep 17 00:00:00 2001 From: Uosis Date: Thu, 12 Jun 2025 08:55:59 -0600 Subject: [PATCH] add name_separator_compat Signed-off-by: Uosis --- docs/Extensions.md | 20 +++++++ newsfragments/name-separator-compat.feature | 1 + podman_compose.py | 28 ++++++--- .../name_separator_compat/__init__.py | 1 + .../docker-compose_compat.yaml | 7 +++ .../docker-compose_default.yaml | 4 ++ ...st_podman_compose_name_separator_compat.py | 57 +++++++++++++++++++ 7 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 newsfragments/name-separator-compat.feature create mode 100644 tests/integration/name_separator_compat/__init__.py create mode 100644 tests/integration/name_separator_compat/docker-compose_compat.yaml create mode 100644 tests/integration/name_separator_compat/docker-compose_default.yaml create mode 100644 tests/integration/name_separator_compat/test_podman_compose_name_separator_compat.py diff --git a/docs/Extensions.md b/docs/Extensions.md index b4ee8a1..bd3c7ef 100644 --- a/docs/Extensions.md +++ b/docs/Extensions.md @@ -139,6 +139,26 @@ The options to the network modes are passed to the `--network` option of the `po as-is. +## Compatibility of name separators between docker-compose and podman-compose + +Currently, podman-compose is using underscores (`_` character) as a separator in names of +containers, images, etc., while docker-compose has switched to hyphens (`-` character). This setting +allows to switch podman-compose to use hyphens as well. + +To enable compatibility between docker-compose and podman-compose, specify +`name_separator_compat: true` under global `x-podman` key: + +``` +x-podman: + name_separator_compat: true +``` + +By default `name_separator_compat` is `false`. This will change to `true` at some point and the +setting will be removed. + +This setting can also be changed by setting `PODMAN_COMPOSE_NAME_SEPARATOR_COMPAT` environment +variable. + ## Compatibility of default network names between docker-compose and podman-compose Current versions of podman-compose may produce different default external network names than diff --git a/newsfragments/name-separator-compat.feature b/newsfragments/name-separator-compat.feature new file mode 100644 index 0000000..e3b11fe --- /dev/null +++ b/newsfragments/name-separator-compat.feature @@ -0,0 +1 @@ +- Add new name_separator_compat x-podman setting to change name separator to hyphen, same as Docker Compose \ No newline at end of file diff --git a/podman_compose.py b/podman_compose.py index 2805d52..68312d8 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -223,11 +223,10 @@ def fix_mount_dict( # handle anonymous or implied volume if not source: # missing source - vol["name"] = "_".join([ - compose.project_name, + vol["name"] = compose.format_name( srv_name, hashlib.sha256(mount_dict["target"].encode("utf-8")).hexdigest(), - ]) + ) elif not name: external = vol.get("external") if isinstance(external, dict): @@ -374,9 +373,8 @@ def default_network_name_for_project(compose: PodmanCompose, net: str, is_ext: A PodmanCompose.XPodmanSettingKey.DEFAULT_NET_NAME_COMPAT, False ) if default_net_name_compat is True: - return f"{compose.project_name.replace('-', '')}_{net}" - return f"{compose.project_name}_{net}" - + return compose.join_name_parts(compose.project_name.replace('-', ''), net) + return compose.format_name(net) # def tr_identity(project_name, given_containers): # pod_name = f'pod_{project_name}' @@ -1973,6 +1971,7 @@ class PodmanCompose: class XPodmanSettingKey(Enum): DEFAULT_NET_NAME_COMPAT = "default_net_name_compat" DEFAULT_NET_BEHAVIOR_COMPAT = "default_net_behavior_compat" + NAME_SEPARATOR_COMPAT = "name_separator_compat" IN_POD = "in_pod" POD_ARGS = "pod_args" @@ -2082,6 +2081,17 @@ class PodmanCompose: PodmanCompose.XPodmanSettingKey.POD_ARGS, ["--infra=false", "--share="] ) + def join_name_parts(self, *parts: str) -> str: + if self.x_podman.get(PodmanCompose.XPodmanSettingKey.NAME_SEPARATOR_COMPAT, False): + sep = "-" + else: + sep = "_" + + return sep.join(parts) + + def format_name(self, *parts: str) -> str: + return self.join_name_parts(self.project_name, *parts) + def _parse_x_podman_settings(self, compose: dict[str, Any], environ: dict[str, str]) -> None: known_keys = {s.value: s for s in PodmanCompose.XPodmanSettingKey} @@ -2352,7 +2362,7 @@ class PodmanCompose: container_names_by_service[service_name] = [] for num in range(1, replicas + 1): - name0 = f"{project_name}_{service_name}_{num}" + name0 = self.format_name(service_name, str(num)) if num == 1: name = service_desc.get("container_name", name0) else: @@ -2368,7 +2378,7 @@ class PodmanCompose: x_podman = service_desc.get("x-podman") rootfs_mode = x_podman is not None and x_podman.get("rootfs") is not None if "image" not in cnt and not rootfs_mode: - cnt["image"] = f"{project_name}_{service_name}" + cnt["image"] = self.format_name(service_name) labels = norm_as_list(cnt.get("labels")) cnt["ports"] = norm_ports(cnt.get("ports")) labels.extend(podman_compose_labels) @@ -3363,7 +3373,7 @@ def compose_run_update_container_from_args( compose: PodmanCompose, cnt: dict, args: argparse.Namespace ) -> None: # adjust one-off container options - name0 = "{}_{}_tmp{}".format(compose.project_name, args.service, random.randrange(0, 65536)) + name0 = compose.format_name(args.service, str(random.randrange(0, 65536))) cnt["name"] = args.name or name0 if args.entrypoint: cnt["entrypoint"] = args.entrypoint diff --git a/tests/integration/name_separator_compat/__init__.py b/tests/integration/name_separator_compat/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/integration/name_separator_compat/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/integration/name_separator_compat/docker-compose_compat.yaml b/tests/integration/name_separator_compat/docker-compose_compat.yaml new file mode 100644 index 0000000..650f31b --- /dev/null +++ b/tests/integration/name_separator_compat/docker-compose_compat.yaml @@ -0,0 +1,7 @@ +services: + web: + image: busybox + command: httpd -f -p 8123 -h /tmp/ + +x-podman: + name_separator_compat: true diff --git a/tests/integration/name_separator_compat/docker-compose_default.yaml b/tests/integration/name_separator_compat/docker-compose_default.yaml new file mode 100644 index 0000000..6ac63ff --- /dev/null +++ b/tests/integration/name_separator_compat/docker-compose_default.yaml @@ -0,0 +1,4 @@ +services: + web: + image: busybox + command: httpd -f -p 8123 -h /tmp/ diff --git a/tests/integration/name_separator_compat/test_podman_compose_name_separator_compat.py b/tests/integration/name_separator_compat/test_podman_compose_name_separator_compat.py new file mode 100644 index 0000000..90d377c --- /dev/null +++ b/tests/integration/name_separator_compat/test_podman_compose_name_separator_compat.py @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0 + +import os +import unittest + +from parameterized import parameterized + +from tests.integration.test_utils import RunSubprocessMixin +from tests.integration.test_utils import podman_compose_path +from tests.integration.test_utils import test_path + + +class TestComposeNameSeparatorCompat(unittest.TestCase, RunSubprocessMixin): + @parameterized.expand([ + ('default', { }, '_'), + ('default', { 'PODMAN_COMPOSE_NAME_SEPARATOR_COMPAT': '1' }, '-'), + ('compat', { }, '-'), + ('compat', { 'PODMAN_COMPOSE_NAME_SEPARATOR_COMPAT': '1' }, '-'), + ('compat', { 'PODMAN_COMPOSE_NAME_SEPARATOR_COMPAT': '0' }, '_'), + ]) + def test_container_name(self, file: str, env: dict[str, str], expected_sep: str) -> None: + compose_yaml_path = os.path.join( + test_path(), + "name_separator_compat", + f"docker-compose_{file}.yaml") + + try: + self.run_subprocess_assert_returncode( + [podman_compose_path(), "-f", compose_yaml_path, "up", "-d"], + env=env, + ) + + container_name_out, _ = self.run_subprocess_assert_returncode( + [ + podman_compose_path(), + "-f", + compose_yaml_path, + "ps", + "--format", + '{{.Names}}', + ], + env=env, + ) + container_name = container_name_out.decode('utf-8').strip() + + expected_container_name = f'name_separator_compat{expected_sep}web{expected_sep}1' + + self.assertEqual(container_name, expected_container_name) + finally: + self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path, + "down", + "-t", + "0", + ], env=env)