diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 3a785a3c..c9eaa38a 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -14,6 +14,9 @@ class DockerCompose: Args: filepath: Relative directory containing the docker compose configuration file. compose_file_name: File name of the docker compose configuration file. + compose_command: The command to use for docker compose. If not specified, a call to + docker compose --help will be made to determine the correct command to use. + If docker compose is not installed, docker-compose will be used. pull: Pull images before launching environment. build: Build images referenced in the configuration file. env_file: Path to an env file containing environment variables to pass to docker compose. @@ -45,6 +48,7 @@ def __init__( self, filepath: str, compose_file_name: Union[str, Iterable] = "docker-compose.yml", + compose_command: str = None, pull: bool = False, build: bool = False, env_file: Optional[str] = None, @@ -57,6 +61,7 @@ def __init__( self.build = build self.env_file = env_file self.services = services + self.compose_command = self._get_compose_command(compose_command) def __enter__(self) -> "DockerCompose": self.start() @@ -65,6 +70,25 @@ def __enter__(self) -> "DockerCompose": def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.stop() + def _get_compose_command(self, command): + """ + Returns the basecommand parts used for the docker compose commands + depending on the docker compose api. + + Returns + ------- + list[str] + The docker compose command parts + """ + if command: + return command.split(" ") + + if subprocess.run(["docker", "compose", "--help"], stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT).returncode == 0: + return ["docker", "compose"] + + return ["docker-compose"] + def docker_compose_command(self) -> List[str]: """ Returns command parts used for the docker compose commands @@ -72,7 +96,7 @@ def docker_compose_command(self) -> List[str]: Returns: cmd: Docker compose command parts. """ - docker_compose_cmd = ['docker-compose'] + docker_compose_cmd = self._get_compose_command[:] for file in self.compose_file_names: docker_compose_cmd += ['-f', file] if self.env_file: @@ -84,22 +108,21 @@ def start(self) -> None: Starts the docker compose environment. """ if self.pull: - pull_cmd = self.docker_compose_command() + ['pull'] + pull_cmd = self.compose_command + ['pull'] self._call_command(cmd=pull_cmd) - up_cmd = self.docker_compose_command() + ['up', '-d'] + up_cmd = self.compose_command + ['up', '-d'] if self.build: up_cmd.append('--build') if self.services: up_cmd.extend(self.services) - self._call_command(cmd=up_cmd) def stop(self) -> None: """ Stops the docker compose environment. """ - down_cmd = self.docker_compose_command() + ['down', '-v'] + down_cmd = self.compose_command + ['down', '-v'] self._call_command(cmd=down_cmd) def get_logs(self) -> Tuple[str, str]: @@ -110,7 +133,7 @@ def get_logs(self) -> Tuple[str, str]: stdout: Standard output stream. stderr: Standard error stream. """ - logs_cmd = self.docker_compose_command() + ["logs"] + logs_cmd = self.compose_command + ["logs"] result = subprocess.run( logs_cmd, cwd=self.filepath, @@ -131,7 +154,7 @@ def exec_in_container(self, service_name: str, command: List[str]) -> Tuple[str, stdout: Standard output stream. stderr: Standard error stream. """ - exec_cmd = self.docker_compose_command() + ['exec', '-T', service_name] + command + exec_cmd = self.compose_command + ['exec', '-T', service_name] + command result = subprocess.run( exec_cmd, cwd=self.filepath, @@ -167,8 +190,11 @@ def get_service_host(self, service_name: str, port: int) -> str: return self._get_service_info(service_name, port)[0] def _get_service_info(self, service: str, port: int) -> List[str]: - port_cmd = self.docker_compose_command() + ["port", service, str(port)] - output = subprocess.check_output(port_cmd, cwd=self.filepath).decode("utf-8") + port_cmd = self.compose_command + ["port", service, str(port)] + try: + output = subprocess.check_output(port_cmd, cwd=self.filepath).decode("utf-8") + except subprocess.CalledProcessError as e: + raise NoSuchPortExposed(str(e.stderr)) result = str(output).rstrip().split(":") if len(result) != 2 or not all(result): raise NoSuchPortExposed(f"port {port} is not exposed for service {service}") diff --git a/compose/tests/test_docker_compose.py b/compose/tests/test_docker_compose.py index da611f1c..8dce2a15 100644 --- a/compose/tests/test_docker_compose.py +++ b/compose/tests/test_docker_compose.py @@ -34,7 +34,8 @@ def test_can_build_images_before_spawning_service_via_compose(): assert compose.build docker_compose_cmd = call_mock.call_args_list[0][1]["cmd"] - assert "docker-compose" in docker_compose_cmd + assert "docker-compose" in docker_compose_cmd or ("docker" in docker_compose_cmd + and "compose" in docker_compose_cmd) assert "up" in docker_compose_cmd assert "--build" in docker_compose_cmd