88This module is designed to simulate the actions of the Data Manager
99and Job Operator that are running in the DM kubernetes deployment.
1010"""
11+
12+ import contextlib
1113import copy
1214import os
1315import shutil
1416import subprocess
17+ import sys
1518import time
16- from typing import Any , Dict , Optional , Tuple
19+ from typing import Any , Dict , List , Optional , Tuple
1720
1821# The 'simulated' instance directory,
1922# created by the Data Manager prior to launching the corresponding Job.
5962"""
6063
6164
65+ def _get_docker_compose_command () -> str :
66+ # Try 'docker compose' (v2) and then 'docker-compose' (v1)
67+ # we need one or the other.
68+ dc_command : str = ""
69+ try :
70+ _ = subprocess .run (
71+ ["docker" , "compose" , "version" ],
72+ capture_output = True ,
73+ check = False ,
74+ timeout = 4 ,
75+ )
76+ dc_command = "docker compose"
77+ except FileNotFoundError :
78+ with contextlib .suppress (FileNotFoundError ):
79+ _ = subprocess .run (
80+ ["docker-compose" , "version" ],
81+ capture_output = True ,
82+ check = False ,
83+ timeout = 4 ,
84+ )
85+ dc_command = "docker-compose"
86+ if not dc_command :
87+ print ("ERROR: Neither 'docker compose' nor 'docker-compose' has been found" )
88+ print ("One of these is required." )
89+ print ("Please install one of them." )
90+ sys .exit (1 )
91+
92+ assert dc_command
93+ return dc_command
94+
95+
6296def _get_docker_compose_version () -> str :
63- result = subprocess . run (
64- [ "docker-compose" , " version" ], capture_output = True , check = False , timeout = 4
65- )
97+ dc_command = _get_docker_compose_command ()
98+ version_cmd : List [ str ] = dc_command . split () + [ " version" ]
99+ result = subprocess . run ( version_cmd , capture_output = True , check = False , timeout = 4 )
66100
67101 # stdout will contain the version on the first line: -
68- # "docker-compose version 1 .29.2, build unknown"
102+ # "docker-compose version v1 .29.2, build unknown"
69103 # Ignore the first 23 characters of the first line...
70104 return str (result .stdout .decode ("utf-8" ).split ("\n " )[0 ][23 :])
71105
@@ -77,10 +111,12 @@ def get_test_root() -> str:
77111
78112
79113class Compose :
80- """A class handling the execution of 'docker- compose'
114+ """A class handling the execution of 'docker compose'
81115 for an individual test.
82116 """
83117
118+ # The docker-compose command (for the first test)
119+ _COMPOSE_COMMAND : Optional [str ] = None
84120 # The docker-compose version (for the first test)
85121 _COMPOSE_VERSION : Optional [str ] = None
86122
@@ -144,10 +180,14 @@ def create(self) -> str:
144180 if os .path .exists (test_path ):
145181 shutil .rmtree (test_path )
146182
183+ # Do we have the command?
184+ if not Compose ._COMPOSE_COMMAND :
185+ Compose ._COMPOSE_COMMAND = _get_docker_compose_command ()
186+ print (f"# Compose command: { Compose ._COMPOSE_COMMAND } " )
147187 # Do we have the docker-compose version the user's installed?
148188 if not Compose ._COMPOSE_VERSION :
149189 Compose ._COMPOSE_VERSION = _get_docker_compose_version ()
150- print (f"# Compose: docker-compose ( { Compose ._COMPOSE_VERSION } ) " )
190+ print (f"# Compose version: { Compose ._COMPOSE_VERSION } " )
151191
152192 # Make the test directory
153193 # (where the test is launched from)
@@ -159,15 +199,8 @@ def create(self) -> str:
159199 os .makedirs (inst_path )
160200
161201 # Run as a specific user/group ID?
162- if self ._user_id is not None :
163- user_id = self ._user_id
164- else :
165- user_id = os .getuid ()
166- if self ._group_id is not None :
167- group_id = self ._group_id
168- else :
169- group_id = os .getgid ()
170-
202+ user_id = self ._user_id if self ._user_id is not None else os .getuid ()
203+ group_id = self ._group_id if self ._group_id is not None else os .getgid ()
171204 # Write the Docker compose content to a file in the test directory
172205 additional_environment : str = ""
173206 if self ._test_environment :
@@ -214,10 +247,11 @@ def run(
214247 caller along with the stdout and stderr content.
215248 A non-zero exit code does not necessarily mean the test has failed.
216249 """
250+ assert Compose ._COMPOSE_COMMAND
217251
218252 execution_directory : str = self .get_test_path ()
219253
220- print ('# Compose: Executing the test ("docker-compose up")...' )
254+ print (f '# Compose: Executing the test ("{ Compose . _COMPOSE_COMMAND } up")...' )
221255 print (f'# Compose: Execution directory is "{ execution_directory } "' )
222256
223257 cwd = os .getcwd ()
@@ -237,23 +271,24 @@ def run(
237271 # we set the prefix for the network name and can use compose files
238272 # from different directories. Without this the network name
239273 # is prefixed by the directory the compose file is in.
274+ up_cmd : List [str ] = Compose ._COMPOSE_COMMAND .split () + [
275+ "-p" ,
276+ "data-manager" ,
277+ "up" ,
278+ "--exit-code-from" ,
279+ "job" ,
280+ "--abort-on-container-exit" ,
281+ ]
240282 test = subprocess .run (
241- [
242- "docker-compose" ,
243- "-p" ,
244- "data-manager" ,
245- "up" ,
246- "--exit-code-from" ,
247- "job" ,
248- "--abort-on-container-exit" ,
249- ],
283+ up_cmd ,
250284 capture_output = True ,
251285 timeout = timeout_minutes * 60 ,
252286 check = False ,
253287 env = env ,
254288 )
289+ down_cmd : List [str ] = Compose ._COMPOSE_COMMAND .split () + ["down" ]
255290 _ = subprocess .run (
256- [ "docker-compose" , "down" ] ,
291+ down_cmd ,
257292 capture_output = True ,
258293 timeout = 240 ,
259294 check = False ,
@@ -279,9 +314,10 @@ def delete(self) -> None:
279314 def run_group_compose_file (compose_file : str , delay_seconds : int = 0 ) -> bool :
280315 """Starts a group compose file in a detached state.
281316 The file is expected to be a compose file in the 'data-manager' directory.
282- We pull the continer imag to reduce the 'docker-compose up' time
317+ We pull the container image to reduce the 'docker-compose up' time
283318 and then optionally wait for a period of seconds.
284319 """
320+ assert Compose ._COMPOSE_COMMAND
285321
286322 print ("# Compose: Starting test group containers..." )
287323
@@ -290,13 +326,13 @@ def run_group_compose_file(compose_file: str, delay_seconds: int = 0) -> bool:
290326 try :
291327 # Pre-pull the docker-compose images.
292328 # This saves start-up execution time.
329+ pull_cmd : List [str ] = Compose ._COMPOSE_COMMAND .split () + [
330+ "-f" ,
331+ os .path .join ("data-manager" , compose_file ),
332+ "pull" ,
333+ ]
293334 _ = subprocess .run (
294- [
295- "docker-compose" ,
296- "-f" ,
297- os .path .join ("data-manager" , compose_file ),
298- "pull" ,
299- ],
335+ pull_cmd ,
300336 capture_output = False ,
301337 check = False ,
302338 )
@@ -306,16 +342,16 @@ def run_group_compose_file(compose_file: str, delay_seconds: int = 0) -> bool:
306342 # we set the prefix for the network name and services from this container
307343 # are visible to the test container. Without this the network name
308344 # is prefixed by the directory the compose file is in.
345+ up_cmd : List [str ] = Compose ._COMPOSE_COMMAND .split () + [
346+ "-f" ,
347+ os .path .join ("data-manager" , compose_file ),
348+ "-p" ,
349+ "data-manager" ,
350+ "up" ,
351+ "-d" ,
352+ ]
309353 _ = subprocess .run (
310- [
311- "docker-compose" ,
312- "-f" ,
313- os .path .join ("data-manager" , compose_file ),
314- "-p" ,
315- "data-manager" ,
316- "up" ,
317- "-d" ,
318- ],
354+ up_cmd ,
319355 capture_output = False ,
320356 check = False ,
321357 )
@@ -335,19 +371,20 @@ def stop_group_compose_file(compose_file: str) -> bool:
335371 """Stops a group compose file.
336372 The file is expected to be a compose file in the 'data-manager' directory.
337373 """
374+ assert Compose ._COMPOSE_COMMAND
338375
339376 print ("# Compose: Stopping test group containers..." )
340377
341378 try :
342379 # Bring the compose file down...
380+ down_cmd : List [str ] = Compose ._COMPOSE_COMMAND .split () + [
381+ "-f" ,
382+ os .path .join ("data-manager" , compose_file ),
383+ "down" ,
384+ "--remove-orphans" ,
385+ ]
343386 _ = subprocess .run (
344- [
345- "docker-compose" ,
346- "-f" ,
347- os .path .join ("data-manager" , compose_file ),
348- "down" ,
349- "--remove-orphans" ,
350- ],
387+ down_cmd ,
351388 capture_output = False ,
352389 timeout = 240 ,
353390 check = False ,
0 commit comments