Skip to content

Commit 5119348

Browse files
committed
Fix service_completed_successfully to require exit code 0
Previously accepted any container stop, now correctly validates exit code 0 matching Docker Compose behavior. Docker Compose implementation reference: https://github.com/docker/compose/blob/v2.29.7/pkg/compose/convergence.go#L433 Signed-off-by: Henrik Schmidt <[email protected]>
1 parent a1f3bef commit 5119348

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

podman_compose.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3156,6 +3156,24 @@ async def check_dep_conditions(compose: PodmanCompose, deps: set) -> None:
31563156
await compose.podman.output(
31573157
[], "wait", [f"--condition={condition.value}"] + deps_cd
31583158
)
3159+
3160+
# service_completed_successfully requires exit code 0
3161+
if condition == ServiceDependencyCondition.STOPPED:
3162+
for container_name in deps_cd:
3163+
inspect_output = await compose.podman.output(
3164+
[], "inspect", [container_name]
3165+
)
3166+
container_info = json.loads(inspect_output)[0]
3167+
3168+
exit_code = container_info.get("State", {}).get("ExitCode", -1)
3169+
if exit_code != 0:
3170+
error_msg = (
3171+
f"Container {container_name} didn't complete successfully: "
3172+
f"exit code {exit_code}"
3173+
)
3174+
log.error(error_msg)
3175+
raise RuntimeError(error_msg)
3176+
31593177
log.debug(
31603178
"dependencies for condition %s have been fulfilled on containers %s",
31613179
condition.value,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
version: "3.7"
2+
services:
3+
failing_oneshot:
4+
image: nopush/podman-compose-test
5+
command: ["sh", "-c", "echo 'Task failed!' && exit 1"]
6+
tmpfs:
7+
- /run
8+
- /tmp
9+
10+
should_not_start:
11+
image: nopush/podman-compose-test
12+
command: ["sh", "-c", "echo 'This should not run' && sleep 3600"]
13+
depends_on:
14+
failing_oneshot:
15+
condition: service_completed_successfully
16+
tmpfs:
17+
- /run
18+
- /tmp
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
version: "3.7"
2+
services:
3+
oneshot:
4+
image: nopush/podman-compose-test
5+
command: ["sh", "-c", "echo 'Task completed successfully' && exit 0"]
6+
tmpfs:
7+
- /run
8+
- /tmp
9+
10+
longrunning:
11+
image: nopush/podman-compose-test
12+
command: ["sh", "-c", "echo 'Starting after oneshot completes' && sleep 3600"]
13+
depends_on:
14+
oneshot:
15+
condition: service_completed_successfully
16+
tmpfs:
17+
- /run
18+
- /tmp

tests/integration/deps/test_podman_compose_deps.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,69 @@ def test_deps_fails(self) -> None:
186186
"down",
187187
])
188188

189+
def test_deps_completed_successfully(self) -> None:
190+
suffix = "-conditional-completed"
191+
try:
192+
self.run_subprocess_assert_returncode([
193+
podman_compose_path(),
194+
"-f",
195+
compose_yaml_path(suffix),
196+
"up",
197+
"-d",
198+
])
199+
200+
output, _ = self.run_subprocess_assert_returncode([
201+
podman_compose_path(),
202+
"-f",
203+
compose_yaml_path(suffix),
204+
"ps",
205+
])
206+
207+
self.assertIn(b"oneshot", output)
208+
self.assertIn(b"Exited (0)", output)
209+
self.assertIn(b"longrunning", output)
210+
self.assertIn(b"Up", output)
211+
212+
finally:
213+
self.run_subprocess_assert_returncode([
214+
podman_compose_path(),
215+
"-f",
216+
compose_yaml_path(suffix),
217+
"down",
218+
])
219+
220+
def test_deps_completed_failed(self) -> None:
221+
suffix = "-conditional-completed-failed"
222+
try:
223+
output, stderr, returncode = self.run_subprocess([
224+
podman_compose_path(),
225+
"-f",
226+
compose_yaml_path(suffix),
227+
"up",
228+
"-d",
229+
])
230+
231+
self.assertNotEqual(returncode, 0)
232+
self.assertIn(b"didn't complete successfully", stderr)
233+
234+
output, _ = self.run_subprocess_assert_returncode([
235+
podman_compose_path(),
236+
"-f",
237+
compose_yaml_path(suffix),
238+
"ps",
239+
])
240+
241+
self.assertIn(b"failing_oneshot", output)
242+
self.assertIn(b"Exited (1)", output)
243+
244+
finally:
245+
self.run_subprocess_assert_returncode([
246+
podman_compose_path(),
247+
"-f",
248+
compose_yaml_path(suffix),
249+
"down",
250+
])
251+
189252

190253
class TestComposeConditionalDepsHealthy(unittest.TestCase, PodmanAwareRunSubprocessMixin):
191254
def setUp(self) -> None:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import unittest
2+
3+
from podman_compose import ServiceDependencyCondition
4+
5+
6+
class TestServiceDependencyCondition(unittest.TestCase):
7+
def test_service_completed_successfully_maps_to_stopped(self) -> None:
8+
condition = ServiceDependencyCondition.from_value("service_completed_successfully")
9+
self.assertEqual(condition, ServiceDependencyCondition.STOPPED)
10+
11+
def test_service_healthy_maps_correctly(self) -> None:
12+
condition = ServiceDependencyCondition.from_value("service_healthy")
13+
self.assertEqual(condition, ServiceDependencyCondition.HEALTHY)
14+
15+
def test_service_started_maps_to_running(self) -> None:
16+
condition = ServiceDependencyCondition.from_value("service_started")
17+
self.assertEqual(condition, ServiceDependencyCondition.RUNNING)
18+
19+
def test_direct_condition_values(self) -> None:
20+
self.assertEqual(
21+
ServiceDependencyCondition.from_value("stopped"),
22+
ServiceDependencyCondition.STOPPED,
23+
)
24+
self.assertEqual(
25+
ServiceDependencyCondition.from_value("healthy"),
26+
ServiceDependencyCondition.HEALTHY,
27+
)
28+
self.assertEqual(
29+
ServiceDependencyCondition.from_value("running"),
30+
ServiceDependencyCondition.RUNNING,
31+
)
32+
33+
def test_invalid_condition_raises_error(self) -> None:
34+
with self.assertRaises(ValueError):
35+
ServiceDependencyCondition.from_value("invalid_condition")

0 commit comments

Comments
 (0)