Merge pull request #1238 from uosis/env-var-override

Add support for providing x-podman settings using environment variables
This commit is contained in:
Povilas Kanapickas 2025-06-11 22:38:25 +03:00 committed by GitHub
commit 9cde3993f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 120 additions and 11 deletions

View File

@ -156,6 +156,9 @@ x-podman:
By default `default_net_name_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_DEFAULT_NET_NAME_COMPAT` environment
variable.
## Compatibility of default network behavior between docker-compose and podman-compose
When there is no network defined (neither network-mode nor networks) in service,
@ -176,6 +179,9 @@ x-podman:
default_net_behavior_compat: true
```
This setting can also be changed by setting `PODMAN_COMPOSE_DEFAULT_NET_BEHAVIOR_COMPAT` environment
variable.
## Custom pods management
Podman-compose can have containers in pods. This can be controlled by extension key x-podman in_pod.
@ -195,6 +201,9 @@ x-podman:
in_pod: false
```
This setting can also be changed by setting `PODMAN_COMPOSE_IN_POD` environment
variable.
It is also possible to override the default arguments for pod creation that are
used when --pod-args is not passed on the command line:
```yml
@ -208,3 +217,6 @@ x-podman:
```
When not set in docker-compose.yml or on the command line, the pod args default
to `["--infra=false", "--share="]`.
This setting can also be changed by setting `PODMAN_COMPOSE_POD_ARGS` environment
variable.

View File

@ -0,0 +1 @@
- Add support for setting x-podman values using PODMAN_COMPOSE_* environment variables.

View File

@ -370,7 +370,9 @@ def default_network_name_for_project(compose: PodmanCompose, net: str, is_ext: A
assert compose.project_name is not None
default_net_name_compat = compose.x_podman.get("default_net_name_compat", False)
default_net_name_compat = compose.x_podman.get(
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}"
@ -1968,6 +1970,12 @@ COMPOSE_DEFAULT_LS = [
class PodmanCompose:
class XPodmanSettingKey(Enum):
DEFAULT_NET_NAME_COMPAT = "default_net_name_compat"
DEFAULT_NET_BEHAVIOR_COMPAT = "default_net_behavior_compat"
IN_POD = "in_pod"
POD_ARGS = "pod_args"
def __init__(self) -> None:
self.podman: Podman
self.podman_version: str | None = None
@ -1988,7 +1996,7 @@ class PodmanCompose:
self.services: dict[str, Any]
self.all_services: set[Any] = set()
self.prefer_volume_over_mount = True
self.x_podman: dict[str, Any] = {}
self.x_podman: dict[PodmanCompose.XPodmanSettingKey, Any] = {}
self.merged_yaml: Any
self.yaml_hash = ""
self.console_colors = [
@ -2059,7 +2067,7 @@ class PodmanCompose:
def resolve_in_pod(self) -> bool:
if self.global_args.in_pod in (None, ''):
self.global_args.in_pod = self.x_podman.get("in_pod", "1")
self.global_args.in_pod = self.x_podman.get(PodmanCompose.XPodmanSettingKey.IN_POD, "1")
# otherwise use `in_pod` value provided by command line
return self.global_args.in_pod
@ -2070,7 +2078,43 @@ class PodmanCompose:
# - Default value
if self.global_args.pod_args is not None:
return shlex.split(self.global_args.pod_args)
return self.x_podman.get("pod_args", ["--infra=false", "--share="])
return self.x_podman.get(
PodmanCompose.XPodmanSettingKey.POD_ARGS, ["--infra=false", "--share="]
)
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}
self.x_podman = {}
for k, v in compose.get("x-podman", {}).items():
known_key = known_keys.get(k)
if known_key:
self.x_podman[known_key] = v
else:
log.warning(
"unknown x-podman key [%s] in compose file, supported keys: %s",
k,
", ".join(known_keys.keys()),
)
env = {
key.removeprefix("PODMAN_COMPOSE_").lower(): value
for key, value in environ.items()
if key.startswith("PODMAN_COMPOSE_")
and key not in {"PODMAN_COMPOSE_PROVIDER", "PODMAN_COMPOSE_WARNING_LOGS"}
}
for k, v in env.items():
known_key = known_keys.get(k)
if known_key:
self.x_podman[known_key] = v
else:
log.warning(
"unknown PODMAN_COMPOSE_ key [%s] in environment, supported keys: %s",
k,
", ".join(known_keys.keys()),
)
def _parse_compose_file(self) -> None:
args = self.global_args
@ -2219,6 +2263,8 @@ class PodmanCompose:
log.debug(" ** merged:\n%s", json.dumps(compose, indent=2))
# ver = compose.get('version')
self._parse_x_podman_settings(compose, self.environ)
services: dict | None = compose.get("services")
if services is None:
services = {}
@ -2236,7 +2282,7 @@ class PodmanCompose:
nets["default"] = None
self.networks = nets
if compose.get("x-podman", {}).get("default_net_behavior_compat", False):
if self.x_podman.get(PodmanCompose.XPodmanSettingKey.DEFAULT_NET_BEHAVIOR_COMPAT, False):
# If there is no network_mode and networks in service,
# docker-compose will create default network named '<project_name>_default'
# and add the service to the default network.
@ -2353,8 +2399,6 @@ class PodmanCompose:
given_containers.sort(key=lambda c: len(c.get("_deps", [])))
# log("sorted:", [c["name"] for c in given_containers])
self.x_podman = compose.get("x-podman", {})
args.in_pod = self.resolve_in_pod()
args.pod_arg_list = self.resolve_pod_args()
pods, containers = transform(args, project_name, given_containers)

View File

@ -467,6 +467,57 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin):
# can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, 1)
def test_x_podman_in_pod_not_exists_command_line_in_pod_not_exists_env_var(self) -> None:
"""
Test that podman-compose will not create a pod when env var is set.
"""
command_up = [
"python3",
os.path.join(base_path(), "podman_compose.py"),
"-f",
os.path.join(
base_path(),
"tests",
"integration",
"in_pod",
"custom_x-podman_not_exists",
"docker-compose.yml",
),
"up",
"-d",
]
down_cmd = [
"python3",
podman_compose_path(),
"-f",
os.path.join(
base_path(),
"tests",
"integration",
"in_pod",
"custom_x-podman_not_exists",
"docker-compose.yml",
),
"down",
]
env = {
"PODMAN_COMPOSE_IN_POD": "0",
}
try:
self.run_subprocess_assert_returncode(
command_up, failure_exitcode_when_rootful(), env=env
)
finally:
self.run_subprocess_assert_returncode(down_cmd, env=env)
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_not_exists"]
# can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, 1)
def test_x_podman_in_pod_custom_name(self) -> None:
"""
Test that podman-compose will create a pod with a custom name

View File

@ -34,7 +34,7 @@ class RunSubprocessMixin:
def is_debug_enabled(self) -> bool:
return "TESTS_DEBUG" in os.environ
def run_subprocess(self, args: list[str]) -> tuple[bytes, bytes, int]:
def run_subprocess(self, args: list[str], env: dict[str, str] = {}) -> tuple[bytes, bytes, int]:
begin = time.time()
if self.is_debug_enabled():
print("TEST_CALL", args)
@ -42,6 +42,7 @@ class RunSubprocessMixin:
args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=os.environ | env,
)
out, err = proc.communicate()
if self.is_debug_enabled():
@ -51,9 +52,9 @@ class RunSubprocessMixin:
return out, err, proc.returncode
def run_subprocess_assert_returncode(
self, args: list[str], expected_returncode: int = 0
self, args: list[str], expected_returncode: int = 0, env: dict[str, str] = {}
) -> tuple[bytes, bytes]:
out, err, returncode = self.run_subprocess(args)
out, err, returncode = self.run_subprocess(args, env=env)
decoded_out = out.decode('utf-8')
decoded_err = err.decode('utf-8')
self.assertEqual( # type: ignore[attr-defined]

View File

@ -650,7 +650,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
self, name: str, is_compat: bool, project_name: str, expected_network_name: str
) -> None:
c = create_compose_mock(project_name)
c.x_podman = {"default_net_name_compat": is_compat}
c.x_podman = {PodmanCompose.XPodmanSettingKey.DEFAULT_NET_NAME_COMPAT: is_compat}
c.networks = {'network1': {}}
cnt = get_minimal_container()