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
20 changes: 20 additions & 0 deletions podman_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -3123,6 +3123,21 @@ def get_excluded(
return excluded


async def _validate_completed_successfully(compose: PodmanCompose, container_names: list) -> None:
"""Validate that containers exited with code 0 for service_completed_successfully"""
for container_name in container_names:
inspect_output = await compose.podman.output([], "inspect", [container_name])
container_info = json.loads(inspect_output)[0]

exit_code = container_info.get("State", {}).get("ExitCode", -1)
if exit_code != 0:
error_msg = (
f"Container {container_name} didn't complete successfully: exit code {exit_code}"
)
log.error(error_msg)
raise RuntimeError(error_msg)


async def check_dep_conditions(compose: PodmanCompose, deps: set) -> None:
"""Enforce that all specified conditions in deps are met"""
if not deps:
Expand Down Expand Up @@ -3156,6 +3171,11 @@ async def check_dep_conditions(compose: PodmanCompose, deps: set) -> None:
await compose.podman.output(
[], "wait", [f"--condition={condition.value}"] + deps_cd
)

# service_completed_successfully requires exit code 0
if condition == ServiceDependencyCondition.STOPPED:
await _validate_completed_successfully(compose, deps_cd)

log.debug(
"dependencies for condition %s have been fulfilled on containers %s",
condition.value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3.7"
services:
failing_oneshot:
image: nopush/podman-compose-test
command: ["sh", "-c", "echo 'Task failed!' && exit 1"]
tmpfs:
- /run
- /tmp

should_not_start:
image: nopush/podman-compose-test
command: ["sh", "-c", "echo 'This should not run' && sleep 3600"]
depends_on:
failing_oneshot:
condition: service_completed_successfully
tmpfs:
- /run
- /tmp
18 changes: 18 additions & 0 deletions tests/integration/deps/docker-compose-conditional-completed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3.7"
services:
oneshot:
image: nopush/podman-compose-test
command: ["sh", "-c", "echo 'Task completed successfully' && exit 0"]
tmpfs:
- /run
- /tmp

longrunning:
image: nopush/podman-compose-test
command: ["sh", "-c", "echo 'Starting after oneshot completes' && sleep 3600"]
depends_on:
oneshot:
condition: service_completed_successfully
tmpfs:
- /run
- /tmp
63 changes: 63 additions & 0 deletions tests/integration/deps/test_podman_compose_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,69 @@ def test_deps_fails(self) -> None:
"down",
])

def test_deps_completed_successfully(self) -> None:
suffix = "-conditional-completed"
try:
self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(suffix),
"up",
"-d",
])

output, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(suffix),
"ps",
])

self.assertIn(b"oneshot", output)
self.assertIn(b"Exited (0)", output)
self.assertIn(b"longrunning", output)
self.assertIn(b"Up", output)

finally:
self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(suffix),
"down",
])

def test_deps_completed_failed(self) -> None:
suffix = "-conditional-completed-failed"
try:
output, stderr, returncode = self.run_subprocess([
podman_compose_path(),
"-f",
compose_yaml_path(suffix),
"up",
"-d",
])

self.assertNotEqual(returncode, 0)
self.assertIn(b"didn't complete successfully", stderr)

output, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(suffix),
"ps",
])

self.assertIn(b"failing_oneshot", output)
self.assertIn(b"Exited (1)", output)

finally:
self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(suffix),
"down",
])


class TestComposeConditionalDepsHealthy(unittest.TestCase, PodmanAwareRunSubprocessMixin):
def setUp(self) -> None:
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/test_service_dependency_condition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import unittest

from podman_compose import ServiceDependencyCondition


class TestServiceDependencyCondition(unittest.TestCase):
def test_service_completed_successfully_maps_to_stopped(self) -> None:
condition = ServiceDependencyCondition.from_value("service_completed_successfully")
self.assertEqual(condition, ServiceDependencyCondition.STOPPED)

def test_service_healthy_maps_correctly(self) -> None:
condition = ServiceDependencyCondition.from_value("service_healthy")
self.assertEqual(condition, ServiceDependencyCondition.HEALTHY)

def test_service_started_maps_to_running(self) -> None:
condition = ServiceDependencyCondition.from_value("service_started")
self.assertEqual(condition, ServiceDependencyCondition.RUNNING)

def test_direct_condition_values(self) -> None:
self.assertEqual(
ServiceDependencyCondition.from_value("stopped"),
ServiceDependencyCondition.STOPPED,
)
self.assertEqual(
ServiceDependencyCondition.from_value("healthy"),
ServiceDependencyCondition.HEALTHY,
)
self.assertEqual(
ServiceDependencyCondition.from_value("running"),
ServiceDependencyCondition.RUNNING,
)

def test_invalid_condition_raises_error(self) -> None:
with self.assertRaises(ValueError):
ServiceDependencyCondition.from_value("invalid_condition")