Skip to content

Commit 30e5223

Browse files
committed
Track dependency conditions when parsing cnditional depends_on
This commit changes the way that depends_on blocks are parsed and tracked during the `podman-compose up` process. Each service's dependencies will be tracked as a dict in which keys are services being depended on and values are the condition to be met by said services before the current service can start. This lays the groundwork for supporting long-syntax / conditional depends_on blocks, but should not change any behavior so far. Signed-off-by: Adrian Torres <[email protected]>
1 parent 7b99b38 commit 30e5223

File tree

3 files changed

+110
-9
lines changed

3 files changed

+110
-9
lines changed

podman_compose.py

+50-8
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,27 @@ def strverscmp_lt(a, b):
102102
return a_ls < b_ls
103103

104104

105+
class DependsCondition: # pylint: disable=too-few-public-methods
106+
# enum for possible types of depends_on conditions
107+
# see https://github.com/compose-spec/compose-spec/blob/master/spec.md#long-syntax-1
108+
STARTED = 0
109+
HEALTHY = 1
110+
COMPLETED = 2
111+
112+
@classmethod
113+
def to_enum(cls, condition):
114+
"""
115+
Converts and returns a condition value into a valid enum value.
116+
"""
117+
if condition == "service_healthy":
118+
return cls.HEALTHY
119+
if condition == "service_completed_successfully":
120+
return cls.COMPLETED
121+
# use cls.STARTED as a catch-all value even
122+
# if the condition value is not within spec
123+
return cls.STARTED
124+
125+
105126
def parse_short_mount(mount_str, basedir):
106127
mount_a = mount_str.split(":")
107128
mount_opt_dict = {}
@@ -987,25 +1008,46 @@ def flat_deps(services, with_extends=False):
9871008
create dependencies "_deps" or update it recursively for all services
9881009
"""
9891010
for name, srv in services.items():
990-
deps = set()
991-
srv["_deps"] = deps
1011+
deps = {}
9921012
if with_extends:
9931013
ext = srv.get("extends", {}).get("service", None)
9941014
if ext:
9951015
if ext != name:
996-
deps.add(ext)
1016+
deps[ext] = DependsCondition.STARTED
9971017
continue
1018+
# NOTE: important that the get call is kept as-is, since depends_on
1019+
# can be an empty string and in that case we want to have an empty list
9981020
deps_ls = srv.get("depends_on", None) or []
9991021
if is_str(deps_ls):
1000-
deps_ls = [deps_ls]
1022+
# depends_on: "foo"
1023+
# treat as condition: service_started
1024+
deps_ls = {deps_ls: DependsCondition.STARTED}
10011025
elif is_dict(deps_ls):
1002-
deps_ls = list(deps_ls.keys())
1003-
deps.update(deps_ls)
1026+
# depends_on:
1027+
# foo:
1028+
# condition: service_xxx
1029+
tmp = {}
1030+
for service, condition in deps_ls.items():
1031+
condition = DependsCondition.to_enum(condition.get("condition"))
1032+
tmp[service] = condition
1033+
deps_ls = tmp
1034+
else:
1035+
# depends_on:
1036+
# - foo
1037+
# treat as condition: service_started
1038+
deps_ls = {dep: DependsCondition.STARTED for dep in deps_ls}
1039+
deps = {**deps, **deps_ls}
10041040
# parse link to get service name and remove alias
1041+
# NOTE: important that the get call is kept as-is, since links can
1042+
# be an empty string and in that case we want to have an empty list
10051043
links_ls = srv.get("links", None) or []
10061044
if not is_list(links_ls):
10071045
links_ls = [links_ls]
1008-
deps.update([(c.split(":")[0] if ":" in c else c) for c in links_ls])
1046+
deps = {
1047+
**deps,
1048+
**{c.split(":")[0]: DependsCondition.STARTED for c in links_ls},
1049+
}
1050+
srv["_deps"] = deps
10091051
for name, srv in services.items():
10101052
rec_deps(services, name)
10111053

@@ -1922,7 +1964,7 @@ def get_excluded(compose, args):
19221964
if args.services:
19231965
excluded = set(compose.services)
19241966
for service in args.services:
1925-
excluded -= compose.services[service]["_deps"]
1967+
excluded -= set(compose.services[service]["_deps"].keys())
19261968
excluded.discard(service)
19271969
log("** excluding: ", excluded)
19281970
return excluded

pytests/test_dependencies.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
3+
from podman_compose import flat_deps, DependsCondition
4+
5+
6+
@pytest.fixture
7+
def basic_services():
8+
return {
9+
"foo": {},
10+
"bar": {
11+
# string dependency
12+
"depends_on": "foo",
13+
},
14+
"baz": {
15+
# list dependency
16+
"depends_on": ["bar"],
17+
},
18+
"ham": {
19+
# dict / conditional dependency
20+
"depends_on": {
21+
"foo": {
22+
"condition": "service_healthy",
23+
},
24+
},
25+
},
26+
}
27+
28+
29+
def test_flat_deps(basic_services):
30+
flat_deps(basic_services)
31+
assert basic_services["foo"]["_deps"] == {}
32+
assert basic_services["bar"]["_deps"] == {"foo": DependsCondition.STARTED}
33+
assert basic_services["baz"]["_deps"] == {
34+
"bar": DependsCondition.STARTED,
35+
"foo": DependsCondition.STARTED,
36+
}
37+
assert basic_services["ham"]["_deps"] == {"foo": DependsCondition.HEALTHY}

tests/deps/docker-compose.yaml

+23-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,26 @@ services:
2121
tmpfs:
2222
- /run
2323
- /tmp
24-
24+
hello_world:
25+
image: busybox
26+
command: ["/bin/busybox", "sh", "-c", "echo 'hello world'"]
27+
depends_on:
28+
sleep:
29+
condition: service_started
30+
tmpfs:
31+
- /run
32+
- /tmp
33+
healthcheck:
34+
test: echo "hello world"
35+
interval: 10s
36+
timeout: 5s
37+
retries: 5
38+
hello_world_2:
39+
image: busybox
40+
command: ["/bin/busybox", "sh", "-c", "echo 'hello world'"]
41+
depends_on:
42+
sleep:
43+
condition: service_healthy
44+
tmpfs:
45+
- /run
46+
- /tmp

0 commit comments

Comments
 (0)