|
1 | 1 | import contextlib |
| 2 | +from os import PathLike |
2 | 3 | from socket import socket |
3 | 4 | from typing import TYPE_CHECKING, Optional, Union |
4 | 5 |
|
5 | 6 | import docker.errors |
6 | 7 | from docker import version |
7 | 8 | from docker.types import EndpointConfig |
| 9 | +from dotenv import dotenv_values |
8 | 10 | from typing_extensions import Self, assert_never |
9 | 11 |
|
10 | 12 | from testcontainers.core.config import ConnectionMode |
11 | 13 | from testcontainers.core.config import testcontainers_config as c |
12 | 14 | from testcontainers.core.docker_client import DockerClient |
13 | | -from testcontainers.core.exceptions import ContainerStartException |
| 15 | +from testcontainers.core.exceptions import ContainerConnectException, ContainerStartException |
14 | 16 | from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID |
15 | 17 | from testcontainers.core.network import Network |
16 | 18 | from testcontainers.core.utils import is_arm, setup_logger |
@@ -57,6 +59,12 @@ def with_env(self, key: str, value: str) -> Self: |
57 | 59 | self.env[key] = value |
58 | 60 | return self |
59 | 61 |
|
| 62 | + def with_env_file(self, env_file: Union[str, PathLike]) -> Self: |
| 63 | + env_values = dotenv_values(env_file) |
| 64 | + for key, value in env_values.items(): |
| 65 | + self.with_env(key, value) |
| 66 | + return self |
| 67 | + |
60 | 68 | def with_bind_ports(self, container: int, host: Optional[int] = None) -> Self: |
61 | 69 | self.ports[container] = host |
62 | 70 | return self |
@@ -147,7 +155,7 @@ def get_exposed_port(self, port: int) -> int: |
147 | 155 | return self.get_docker_client().port(self._container.id, port) |
148 | 156 | return port |
149 | 157 |
|
150 | | - def with_command(self, command: str) -> Self: |
| 158 | + def with_command(self, command: Union[str, list[str]]) -> Self: |
151 | 159 | self._command = command |
152 | 160 | return self |
153 | 161 |
|
@@ -220,15 +228,21 @@ def _create_instance(cls) -> "Reaper": |
220 | 228 | .with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout) |
221 | 229 | .start() |
222 | 230 | ) |
223 | | - wait_for_logs(Reaper._container, r".* Started!") |
| 231 | + wait_for_logs(Reaper._container, r".* Started!", timeout=20, raise_on_exit=True) |
224 | 232 |
|
225 | 233 | container_host = Reaper._container.get_container_host_ip() |
226 | 234 | container_port = int(Reaper._container.get_exposed_port(8080)) |
227 | 235 |
|
| 236 | + if not container_host or not container_port: |
| 237 | + raise ContainerConnectException( |
| 238 | + f"Could not obtain network details for {Reaper._container._container.id}. Host: {container_host} Port: {container_port}" |
| 239 | + ) |
| 240 | + |
228 | 241 | last_connection_exception: Optional[Exception] = None |
229 | 242 | for _ in range(50): |
230 | 243 | try: |
231 | 244 | Reaper._socket = socket() |
| 245 | + Reaper._socket.settimeout(1) |
232 | 246 | Reaper._socket.connect((container_host, container_port)) |
233 | 247 | last_connection_exception = None |
234 | 248 | break |
|
0 commit comments