Skip to content

Commit fcbf15e

Browse files
committed
Wait for depends_on conditions before starting containers
This commit implements the long-syntax / conditional depends_on compose mechanism which instructs the compose system to wait for a certain condition before starting a container. Currently available conditions are: - service_started: same behavior as before this commit, the depending container will start as soon as the depended on container has started - service_healthy: if the depended on container has a healthcheck, wait until said container is marked as healthy before starting the depending container - service_completed_successfully: wait until the depended on container has exited and its exit code is 0, after which the depending container can be started This mechanism is part of the v3 [1] compose spec and is useful for controlling container startup based on other containers that can take a certain amount of time to start or on containers that do complicated setups and must exit before starting other containers. [1] https://red.ht/conditional-depends Signed-off-by: Adrian Torres <[email protected]>
1 parent 30e5223 commit fcbf15e

File tree

2 files changed

+95
-9
lines changed

2 files changed

+95
-9
lines changed

podman_compose.py

+56-2
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ def to_enum(cls, condition):
123123
return cls.STARTED
124124

125125

126+
def wait(func):
127+
def wrapper(*args, **kwargs):
128+
while not func(*args, **kwargs):
129+
time.sleep(0.5)
130+
131+
return wrapper
132+
133+
126134
def parse_short_mount(mount_str, basedir):
127135
mount_a = mount_str.split(":")
128136
mount_opt_dict = {}
@@ -1087,7 +1095,7 @@ def run(
10871095
podman_args,
10881096
cmd="",
10891097
cmd_args=None,
1090-
wait=True,
1098+
_wait=True,
10911099
sleep=1,
10921100
obj=None,
10931101
log_formatter=None,
@@ -1113,7 +1121,7 @@ def run(
11131121
else:
11141122
p = subprocess.Popen(cmd_ls) # pylint: disable=consider-using-with
11151123

1116-
if wait:
1124+
if _wait:
11171125
exit_code = p.wait()
11181126
log("exit code:", exit_code)
11191127
if obj is not None:
@@ -1970,6 +1978,49 @@ def get_excluded(compose, args):
19701978
return excluded
19711979

19721980

1981+
@wait
1982+
def wait_healthy(compose, container_name):
1983+
info = json.loads(compose.podman.output([], "inspect", [container_name]))[0]
1984+
1985+
if not info["Config"].get("Healthcheck"):
1986+
raise ValueError("Container %s does not define a health check" % container_name)
1987+
1988+
health = info["State"]["Healthcheck"]["Status"]
1989+
if health == "unhealthy":
1990+
raise RuntimeError(
1991+
"Container %s is in unhealthy state, aborting" % container_name
1992+
)
1993+
return health == "healthy"
1994+
1995+
1996+
@wait
1997+
def wait_completed(compose, container_name):
1998+
info = json.loads(compose.podman.output([], "inspect", [container_name]))[0]
1999+
2000+
if info["State"]["Status"] == "exited":
2001+
exit_code = info["State"]["ExitCode"]
2002+
if exit_code != 0:
2003+
raise RuntimeError(
2004+
"Container %s didn't complete successfully, exit code: %d"
2005+
% (container_name, exit_code)
2006+
)
2007+
return True
2008+
return False
2009+
2010+
2011+
def wait_for_dependencies(compose, container):
2012+
for dep, condition in container["_deps"].items():
2013+
dep_container_name = compose.container_names_by_service[dep][0]
2014+
if condition == DependsCondition.STARTED:
2015+
# ignore -- will be handled by container order
2016+
continue
2017+
if condition == DependsCondition.HEALTHY:
2018+
wait_healthy(compose, dep_container_name)
2019+
else:
2020+
# implies DependsCondition.COMPLETED
2021+
wait_completed(compose, dep_container_name)
2022+
2023+
19732024
@cmd_run(
19742025
podman_compose, "up", "Create and start the entire stack or some of its services"
19752026
)
@@ -2012,6 +2063,8 @@ def compose_up(compose, args):
20122063
log("** skipping: ", cnt["name"])
20132064
continue
20142065
podman_args = container_to_args(compose, cnt, detached=args.detach)
2066+
if podman_command == "run":
2067+
wait_for_dependencies(compose, cnt)
20152068
subproc = compose.podman.run([], podman_command, podman_args)
20162069
if podman_command == "run" and subproc and subproc.returncode:
20172070
compose.podman.run([], "start", [cnt["name"]])
@@ -2047,6 +2100,7 @@ def compose_up(compose, args):
20472100
continue
20482101
# TODO: remove sleep from podman.run
20492102
obj = compose if exit_code_from == cnt["_service"] else None
2103+
wait_for_dependencies(compose, cnt)
20502104
thread = Thread(
20512105
target=compose.podman.run,
20522106
args=[[], "start", ["-a", cnt["name"]]],

tests/deps/docker-compose.yaml

+39-7
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,26 @@ services:
66
tmpfs:
77
- /run
88
- /tmp
9+
healthcheck:
10+
# test that httpd is running, the brackets [] thing is a trick
11+
# to ignore the grep process returned by ps, meaning that this
12+
# should only be true if httpd is currently running
13+
test: ps | grep "[h]ttpd"
14+
interval: 10s
15+
timeout: 5s
16+
retries: 5
917
sleep:
1018
image: busybox
1119
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
1220
depends_on: "web"
1321
tmpfs:
1422
- /run
1523
- /tmp
24+
healthcheck:
25+
test: sleep 15
26+
interval: 10s
27+
timeout: 20s
28+
retries: 5
1629
sleep2:
1730
image: busybox
1831
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
@@ -21,7 +34,13 @@ services:
2134
tmpfs:
2235
- /run
2336
- /tmp
24-
hello_world:
37+
setup:
38+
image: busybox
39+
command: ["/bin/busybox", "sh", "-c", "sleep 30"]
40+
tmpfs:
41+
- /run
42+
- /tmp
43+
wait_started:
2544
image: busybox
2645
command: ["/bin/busybox", "sh", "-c", "echo 'hello world'"]
2746
depends_on:
@@ -30,12 +49,16 @@ services:
3049
tmpfs:
3150
- /run
3251
- /tmp
33-
healthcheck:
34-
test: echo "hello world"
35-
interval: 10s
36-
timeout: 5s
37-
retries: 5
38-
hello_world_2:
52+
wait_healthy:
53+
image: busybox
54+
command: ["/bin/busybox", "sh", "-c", "echo 'hello world'"]
55+
depends_on:
56+
web:
57+
condition: service_healthy
58+
tmpfs:
59+
- /run
60+
- /tmp
61+
wait_multiple_healthchecks:
3962
image: busybox
4063
command: ["/bin/busybox", "sh", "-c", "echo 'hello world'"]
4164
depends_on:
@@ -44,3 +67,12 @@ services:
4467
tmpfs:
4568
- /run
4669
- /tmp
70+
wait_completed_successfully:
71+
image: busybox
72+
command: ["/bin/busybox", "sh", "-c", "echo 'hello world'"]
73+
depends_on:
74+
setup:
75+
condition: service_completed_successfully
76+
tmpfs:
77+
- /run
78+
- /tmp

0 commit comments

Comments
 (0)