@@ -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+ )
11831192REQ_TESTGEN_IMAGE = Requirement (
11841193 "TESTGEN_IMAGE" ,
11851194 ("docker" , "manifest" , "inspect" , "{image}" ),
@@ -1413,7 +1422,7 @@ def delete_compose_volumes(self, args):
14131422
14141423class 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.
0 commit comments