2
2
from typing import Iterable , List , Optional , Tuple , Union
3
3
4
4
import requests
5
+ from functools import cached_property
5
6
6
7
from testcontainers .core .exceptions import NoSuchPortExposed
7
8
from testcontainers .core .waiting_utils import wait_container_is_ready
@@ -14,6 +15,9 @@ class DockerCompose:
14
15
Args:
15
16
filepath: Relative directory containing the docker compose configuration file.
16
17
compose_file_name: File name of the docker compose configuration file.
18
+ compose_command: The command to use for docker compose. If not specified, a call to
19
+ docker compose --help will be made to determine the correct command to use.
20
+ If docker compose is not installed, docker-compose will be used.
17
21
pull: Pull images before launching environment.
18
22
build: Build images referenced in the configuration file.
19
23
env_file: Path to an env file containing environment variables to pass to docker compose.
@@ -45,6 +49,7 @@ def __init__(
45
49
self ,
46
50
filepath : str ,
47
51
compose_file_name : Union [str , Iterable ] = "docker-compose.yml" ,
52
+ compose_command : str = None ,
48
53
pull : bool = False ,
49
54
build : bool = False ,
50
55
env_file : Optional [str ] = None ,
@@ -57,6 +62,7 @@ def __init__(
57
62
self .build = build
58
63
self .env_file = env_file
59
64
self .services = services
65
+ self ._user_defined_compose_command = compose_command .split (" " ) if compose_command else None
60
66
61
67
def __enter__ (self ) -> "DockerCompose" :
62
68
self .start ()
@@ -65,14 +71,30 @@ def __enter__(self) -> "DockerCompose":
65
71
def __exit__ (self , exc_type , exc_val , exc_tb ) -> None :
66
72
self .stop ()
67
73
74
+ @cached_property
75
+ def base_docker_compose (self ):
76
+ """
77
+ Returns the basecommand parts used for the docker compose commands depending on the docker compose api
78
+
79
+ Returns
80
+ -------
81
+ list[str]
82
+ The docker compose command parts
83
+ """
84
+ if self ._user_defined_compose_command :
85
+ return self ._user_defined_compose_command
86
+
87
+ return ["docker" ,"compose" ] if subprocess .run (["docker" , "compose" , "--help" ], stdout = subprocess .DEVNULL ,
88
+ stderr = subprocess .STDOUT ).returncode == 0 else ["docker-compose" ]
89
+
68
90
def docker_compose_command (self ) -> List [str ]:
69
91
"""
70
92
Returns command parts used for the docker compose commands
71
93
72
94
Returns:
73
95
cmd: Docker compose command parts.
74
96
"""
75
- docker_compose_cmd = [ 'docker-compose' ]
97
+ docker_compose_cmd = self . base_docker_compose [: ]
76
98
for file in self .compose_file_names :
77
99
docker_compose_cmd += ['-f' , file ]
78
100
if self .env_file :
@@ -92,7 +114,6 @@ def start(self) -> None:
92
114
up_cmd .append ('--build' )
93
115
if self .services :
94
116
up_cmd .extend (self .services )
95
-
96
117
self ._call_command (cmd = up_cmd )
97
118
98
119
def stop (self ) -> None :
@@ -168,7 +189,10 @@ def get_service_host(self, service_name: str, port: int) -> str:
168
189
169
190
def _get_service_info (self , service : str , port : int ) -> List [str ]:
170
191
port_cmd = self .docker_compose_command () + ["port" , service , str (port )]
171
- output = subprocess .check_output (port_cmd , cwd = self .filepath ).decode ("utf-8" )
192
+ try :
193
+ output = subprocess .check_output (port_cmd , cwd = self .filepath ).decode ("utf-8" )
194
+ except subprocess .CalledProcessError as e :
195
+ raise NoSuchPortExposed (str (e .stderr ))
172
196
result = str (output ).rstrip ().split (":" )
173
197
if len (result ) != 2 or not all (result ):
174
198
raise NoSuchPortExposed (f"port { port } is not exposed for service { service } " )
0 commit comments