Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/pod_network_inheritance.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add x-podman.pod_network setting to enable pod network inheritance for external/macvlan networks.
43 changes: 43 additions & 0 deletions podman_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,13 @@ def get_net_args_from_network_mode(compose: PodmanCompose, cnt: dict[str, Any])


def get_net_args(compose: PodmanCompose, cnt: dict[str, Any]) -> list[str]:
# If container should inherit network from pod, skip network args
# This allows podman to automatically set NetNS=FromPod when --pod is
# passed without --network, enabling pod network inheritance for
# macvlan, external networks, or other pod-level network configurations.
if compose.should_inherit_net_from_pod(cnt):
return []

net = cnt.get("network_mode")
if net:
return get_net_args_from_network_mode(compose, cnt)
Expand Down Expand Up @@ -2053,6 +2060,7 @@ class XPodmanSettingKey(Enum):
NAME_SEPARATOR_COMPAT = "name_separator_compat"
IN_POD = "in_pod"
POD_ARGS = "pod_args"
POD_NETWORK = "pod_network"

def __init__(self) -> None:
self.podman: Podman
Expand Down Expand Up @@ -2193,6 +2201,33 @@ def resolve_pod_args(self) -> list[str]:
PodmanCompose.XPodmanSettingKey.POD_ARGS, ["--infra=false", "--share="]
)

def resolve_pod_network(self) -> str | None:
"""
Returns the network name for the pod if pod_network is configured.
This enables pod network inheritance where containers join the pod's
network namespace instead of having their own network configuration.
"""
return self.x_podman.get(PodmanCompose.XPodmanSettingKey.POD_NETWORK)

def should_inherit_net_from_pod(self, cnt: dict[str, Any]) -> bool:
"""
Determines if a container should inherit its network from the pod.
This is true when:
1. The container is assigned to a pod
2. pod_network is configured (indicating the pod has network settings)
3. The container doesn't have explicit network_mode or networks set
"""
pod_network = self.resolve_pod_network()
if not pod_network:
return False
pod = cnt.get("pod", "")
if not pod:
return False
# If container has explicit network configuration, don't inherit from pod
if cnt.get("network_mode") or cnt.get("networks"):
return False
return True

def join_name_parts(self, *parts: str) -> str:
setting = self.x_podman.get(PodmanCompose.XPodmanSettingKey.NAME_SEPARATOR_COMPAT, False)
if try_parse_bool(setting):
Expand Down Expand Up @@ -3101,6 +3136,14 @@ async def create_pods(compose: PodmanCompose) -> None:
"--name=" + pod["name"],
] + compose.resolve_pod_args()

# Add network to pod if pod_network is configured
# This enables pod network inheritance where containers join the pod's
# network namespace (via --pod without --network) instead of having
# their own network configuration.
pod_network = compose.resolve_pod_network()
if pod_network:
podman_args.append(f"--network={pod_network}")

ports = pod.get("ports", [])
if isinstance(ports, str):
ports = [ports]
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/test_container_to_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def create_compose_mock(project_name: str = "test_project_name") -> PodmanCompos
compose.x_podman = {}
compose.join_name_parts = mock.Mock(side_effect=lambda *args: '_'.join(args))
compose.format_name = mock.Mock(side_effect=lambda *args: '_'.join([project_name, *args]))
# Default behavior: don't inherit network from pod (backwards compatible)
compose.should_inherit_net_from_pod = mock.Mock(return_value=False)

async def podman_output(*args: Any, **kwargs: Any) -> None:
pass
Expand Down
100 changes: 100 additions & 0 deletions tests/unit/test_get_net_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,103 @@ def test_network__mode_service(self) -> None:
expected_args = ["--network=container:container_2"]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)

def test_pod_network_inheritance_enabled(self) -> None:
"""
Test that when pod_network is configured and container is in a pod,
get_net_args returns empty list to allow pod network inheritance.
"""
from podman_compose import PodmanCompose

compose = get_networked_compose()
# Set up pod_network via x_podman
compose.x_podman = {PodmanCompose.XPodmanSettingKey.POD_NETWORK: "external_macvlan"}
# Mock the methods that use x_podman
compose.resolve_pod_network = lambda: "external_macvlan"
compose.should_inherit_net_from_pod = lambda cnt: (
compose.resolve_pod_network() is not None
and cnt.get("pod")
and not cnt.get("network_mode")
and not cnt.get("networks")
)

container = get_minimal_container()
container["pod"] = "pod_test_project_name"

expected_args: list = []
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)

def test_pod_network_inheritance_disabled_no_pod(self) -> None:
"""
Test that when pod_network is configured but container is not in a pod,
get_net_args returns normal network args.
"""
from podman_compose import PodmanCompose

compose = get_networked_compose()
compose.x_podman = {PodmanCompose.XPodmanSettingKey.POD_NETWORK: "external_macvlan"}
compose.resolve_pod_network = lambda: "external_macvlan"
compose.should_inherit_net_from_pod = lambda cnt: (
compose.resolve_pod_network() is not None
and cnt.get("pod")
and not cnt.get("network_mode")
and not cnt.get("networks")
)

container = get_minimal_container()
# No pod assigned

expected_args = [f"--network={PROJECT_NAME}_net0:alias={SERVICE_NAME}"]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)

def test_pod_network_inheritance_disabled_explicit_networks(self) -> None:
"""
Test that when container has explicit networks config,
pod network inheritance is skipped even if pod_network is set.
"""
from podman_compose import PodmanCompose

compose = get_networked_compose()
compose.x_podman = {PodmanCompose.XPodmanSettingKey.POD_NETWORK: "external_macvlan"}
compose.resolve_pod_network = lambda: "external_macvlan"
compose.should_inherit_net_from_pod = lambda cnt: (
compose.resolve_pod_network() is not None
and cnt.get("pod")
and not cnt.get("network_mode")
and not cnt.get("networks")
)

container = get_minimal_container()
container["pod"] = "pod_test_project_name"
container["networks"] = {"net0": {}} # Explicit networks config

expected_args = [f"--network={PROJECT_NAME}_net0:alias={SERVICE_NAME}"]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)

def test_pod_network_inheritance_disabled_network_mode(self) -> None:
"""
Test that when container has explicit network_mode,
pod network inheritance is skipped even if pod_network is set.
"""
from podman_compose import PodmanCompose

compose = get_networked_compose()
compose.x_podman = {PodmanCompose.XPodmanSettingKey.POD_NETWORK: "external_macvlan"}
compose.resolve_pod_network = lambda: "external_macvlan"
compose.should_inherit_net_from_pod = lambda cnt: (
compose.resolve_pod_network() is not None
and cnt.get("pod")
and not cnt.get("network_mode")
and not cnt.get("networks")
)

container = get_minimal_container()
container["pod"] = "pod_test_project_name"
container["network_mode"] = "host" # Explicit network_mode

expected_args = ["--network=host"]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
Loading