Skip to content

Commit 168a27e

Browse files
committed
fix: add Docker Compose as separate requirement
1 parent 32d5775 commit 168a27e

3 files changed

Lines changed: 45 additions & 18 deletions

File tree

dk-installer.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,15 @@ def run(self, parent=None):
11801180
("The Docker engine is not running.", "Start the Docker engine and try again."),
11811181
label="Docker engine running",
11821182
)
1183+
REQ_DOCKER_COMPOSE = Requirement(
1184+
"DOCKER_COMPOSE",
1185+
("docker", "compose", "version"),
1186+
(
1187+
"The Docker Compose plugin is not available.",
1188+
"Install the Docker Compose plugin and try again.",
1189+
),
1190+
label="Docker Compose installed",
1191+
)
11831192
REQ_TESTGEN_IMAGE = Requirement(
11841193
"TESTGEN_IMAGE",
11851194
("docker", "manifest", "inspect", "{image}"),
@@ -1413,7 +1422,7 @@ def delete_compose_volumes(self, args):
14131422

14141423
class ComposeDeleteAction(Action, ComposeActionMixin):
14151424
args_cmd = "delete"
1416-
requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON]
1425+
requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
14171426

14181427
def execute(self, args):
14191428
if self.get_compose_file_path(args).exists():
@@ -1676,7 +1685,7 @@ class ObsUpgradeAction(MultiStepAction, ComposeActionMixin):
16761685
label = "Upgrade"
16771686
title = "Upgrade Observability"
16781687
args_cmd = "upgrade"
1679-
requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON]
1688+
requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
16801689

16811690
steps = [
16821691
ObsFetchCurrentVersionStep,
@@ -1919,7 +1928,7 @@ class ObsInstallAction(AnalyticsMultiStepAction, ComposeActionMixin):
19191928
intro_text = ["This process may take 5~15 minutes depending on your system resources and network speed."]
19201929

19211930
args_cmd = "install"
1922-
requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON]
1931+
requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
19231932

19241933
def get_parser(self, sub_parsers):
19251934
parser = super().get_parser(sub_parsers)
@@ -2697,7 +2706,7 @@ class TestgenInstallAction(ComposeActionMixin, AnalyticsMultiStepAction):
26972706
"Installing TestGen with Docker Compose.",
26982707
"This process may take 5~10 minutes depending on your system resources and network speed.",
26992708
]
2700-
docker_requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_TESTGEN_IMAGE]
2709+
docker_requirements = [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE, REQ_TESTGEN_IMAGE]
27012710

27022711
args_cmd = "install"
27032712
label = "Installation"
@@ -2820,7 +2829,12 @@ def _auto_select_mode(self, args):
28202829
CONSOLE.msg("[d] Docker Compose (Recommended)")
28212830
CONSOLE.msg(" The most stable TestGen experience for persistent use.")
28222831
CONSOLE.msg(" Provides a fully managed environment with an isolated PostgreSQL container.")
2823-
prereq_status = " ".join(f"{'(✓)' if ok else '(X)'} {req.label or req.key}" for req, ok in prereq_results)
2832+
# Hide REQ_DOCKER from the picker — REQ_DOCKER_COMPOSE failure implies
2833+
# the same fix, so showing both bloats the prereq line. The actual
2834+
# check below (and the per-prereq fail messages later) still uses all four.
2835+
prereq_status = " ".join(
2836+
f"{'(✓)' if ok else '(X)'} {req.label or req.key}" for req, ok in prereq_results if req is not REQ_DOCKER
2837+
)
28242838
CONSOLE.msg(f" Prerequisites: {prereq_status}")
28252839
CONSOLE.space()
28262840
CONSOLE.msg("[p] Pip + embedded PostgreSQL")
@@ -2925,6 +2939,7 @@ def get_requirements(self, args):
29252939
return [
29262940
REQ_DOCKER,
29272941
REQ_DOCKER_DAEMON,
2942+
REQ_DOCKER_COMPOSE,
29282943
Requirement(
29292944
"TG_COMPOSE_FILE",
29302945
(
@@ -2978,7 +2993,7 @@ def check_requirements(self, args):
29782993

29792994
def get_requirements(self, args):
29802995
if self._resolved_mode == INSTALL_MODE_DOCKER:
2981-
return [REQ_DOCKER, REQ_DOCKER_DAEMON]
2996+
return [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
29822997
return []
29832998

29842999
def _resolve_install_mode(self, args):
@@ -3052,7 +3067,7 @@ def check_requirements(self, args):
30523067

30533068
def get_requirements(self, args):
30543069
if self._resolved_mode == INSTALL_MODE_DOCKER:
3055-
return [REQ_DOCKER, REQ_DOCKER_DAEMON]
3070+
return [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
30563071
return []
30573072

30583073
def _resolve_install_mode(self, args):
@@ -3145,10 +3160,14 @@ def check_requirements(self, args):
31453160
super().check_requirements(args)
31463161

31473162
def get_requirements(self, args):
3148-
# Docker mode requires Docker. For pip mode, Docker is only needed when
3149-
# the user asked to export to Observability (the dk-demo container
3150-
# generates the export payload).
3151-
if self._resolved_mode == INSTALL_MODE_DOCKER or getattr(args, "obs_export", False):
3163+
# Docker mode requires Docker + Compose (we shell into the engine
3164+
# container via ``docker compose exec``). For pip mode, Docker is only
3165+
# needed when the user asked to export to Observability — the dk-demo
3166+
# container that generates the export payload runs via ``docker run``,
3167+
# so Compose isn't required there.
3168+
if self._resolved_mode == INSTALL_MODE_DOCKER:
3169+
return [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
3170+
if getattr(args, "obs_export", False):
31523171
return [REQ_DOCKER, REQ_DOCKER_DAEMON]
31533172
return []
31543173

@@ -3219,9 +3238,13 @@ def check_requirements(self, args):
32193238
super().check_requirements(args)
32203239

32213240
def get_requirements(self, args):
3222-
# Docker mode requires Docker. For pip mode, the dk-demo container
3223-
# call below is wrapped in try/except so Docker absence is non-fatal.
3224-
return [REQ_DOCKER, REQ_DOCKER_DAEMON] if self._resolved_mode == INSTALL_MODE_DOCKER else []
3241+
# Docker mode requires Docker + Compose (we shell into the engine
3242+
# container via ``docker compose exec``). For pip mode, the dk-demo
3243+
# container call below is wrapped in try/except so Docker absence is
3244+
# non-fatal.
3245+
if self._resolved_mode == INSTALL_MODE_DOCKER:
3246+
return [REQ_DOCKER, REQ_DOCKER_DAEMON, REQ_DOCKER_COMPOSE]
3247+
return []
32253248

32263249
def _resolve_install_mode(self, args):
32273250
# Like delete: idempotent, so "no install" returns rather than aborts.

tests/test_tg_pip_install.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,9 @@ def test_auto_mode_picks_pip_when_docker_unavailable(install_action, args_mock,
204204
@pytest.mark.integration
205205
def test_auto_mode_displays_prereq_status_when_docker_unavailable(install_action, args_mock, console_msg_mock):
206206
"""Docker probe fails → the prereq display lists each requirement with a marker and (for failures) a fix hint."""
207-
# Only the first prereq passes — exercises the mixed pass/fail rendering.
207+
# Only the Compose plugin probe passes — exercises the mixed pass/fail rendering.
208208
def selective_check(req_self, *_, **__):
209-
return req_self.key == "DOCKER"
209+
return req_self.key == "DOCKER_COMPOSE"
210210

211211
with (
212212
patch("tests.installer.Requirement.check_availability", autospec=True, side_effect=selective_check),
@@ -216,8 +216,12 @@ def selective_check(req_self, *_, **__):
216216

217217
console_msg_mock.assert_any_msg_contains("two installation modes")
218218
console_msg_mock.assert_any_msg_contains("Prerequisites:")
219-
console_msg_mock.assert_any_msg_contains("(✓) Docker installed")
219+
console_msg_mock.assert_any_msg_contains("(✓) Docker Compose installed")
220220
console_msg_mock.assert_any_msg_contains("(X) Docker engine running")
221+
# REQ_DOCKER is checked but not displayed — REQ_DOCKER_COMPOSE failure
222+
# implies the same fix, so the picker hides it to keep the line short.
223+
rendered = " ".join(call.args[0] for call in console_msg_mock.call_args_list if call.args)
224+
assert "Docker installed" not in rendered
221225

222226

223227
@pytest.mark.integration

tests/test_tg_upgrade.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def set_version_check_mock(version_check_mock, latest_version):
7777

7878
@pytest.mark.integration
7979
def test_tg_upgrade_compose_missing(tg_upgrade_action, args_mock, start_cmd_mock, console_msg_mock):
80-
start_cmd_mock.__exit__.side_effect = [None, None, CommandFailed]
80+
start_cmd_mock.__exit__.side_effect = [None, None, None, CommandFailed]
8181

8282
with pytest.raises(AbortAction, match=""):
8383
tg_upgrade_action.check_requirements(args_mock)

0 commit comments

Comments
 (0)