Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions core/testcontainers/core/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,9 @@ def start(self) -> Self:
else {}
)

self._container = docker_client.run(
self._container = docker_client.create(
self.image,
command=self._command,
detach=True,
environment=self.env,
ports=cast("dict[int, Optional[int]]", self.ports),
name=self._name,
Expand All @@ -214,14 +213,16 @@ def start(self) -> Self:
**{**network_kwargs, **self._kwargs},
)

for t in self._transferable_specs:
self._transfer_into_container(*t)

docker_client.start(self._container)

if self._wait_strategy is not None:
self._wait_strategy.wait_until_ready(self)

logger.info("Container started: %s", self._container.short_id)

for t in self._transferable_specs:
self._transfer_into_container(*t)

return self

def stop(self, force: bool = True, delete_volume: bool = True) -> None:
Expand Down Expand Up @@ -328,6 +329,13 @@ def exec(self, command: Union[str, list[str]]) -> ExecResult:
raise ContainerStartException("Container should be started before executing a command")
return self._container.exec_run(command)

def wait(self) -> int:
"""Wait for the container to stop and return its exit code."""
if not self._container:
raise ContainerStartException("Container should be started before waiting")
result = self._container.wait()
return int(result["StatusCode"])

def _configure(self) -> None:
# placeholder if subclasses want to define this and use the default start method
pass
Expand Down
37 changes: 37 additions & 0 deletions core/testcontainers/core/docker_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,43 @@ def run(
)
return container

@_wrapped_container_collection
def create(
self,
image: str,
command: Optional[Union[str, list[str]]] = None,
environment: Optional[dict[str, str]] = None,
ports: Optional[dict[int, Optional[int]]] = None,
labels: Optional[dict[str, str]] = None,
**kwargs: Any,
) -> Container:
"""Create a container without starting it, pulling the image first if not present locally."""
if "network" not in kwargs and not get_docker_host():
host_network = self.find_host_network()
if host_network:
kwargs["network"] = host_network

try:
# This is more or less a replication of what the self.client.containers.start does internally
self.client.images.get(image)
except docker.errors.ImageNotFound:
self.client.images.pull(image)

container = self.client.containers.create(
image,
command=command,
environment=environment,
ports=ports,
labels=create_labels(image, labels),
**kwargs,
)
return container

@_wrapped_container_collection
def start(self, container: Container) -> None:
"""Start a previously created container."""
container.start()

@_wrapped_image_collection
def build(
self, path: str, tag: Optional[str], rm: bool = True, **kwargs: Any
Expand Down
13 changes: 13 additions & 0 deletions core/tests/test_transferable.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ def test_copy_into_container_at_startup(transferable: Transferable):
assert result.output == b"hello world"


def test_copy_into_startup_file(transferable: Transferable):
destination_in_container = "/tmp/my_file"

container = DockerContainer("bash", command=f"cat {destination_in_container}")
container.with_copy_into_container(transferable, destination_in_container)

with container:
exit_code = container.wait()
stdout, _ = container.get_logs()
assert exit_code == 0
assert stdout.decode() == "hello world"


def test_copy_into_container_via_initializer(transferable: Transferable):
destination_in_container = "/tmp/my_file"
transferables: list[TransferSpec] = [(transferable, destination_in_container, 0o644)]
Expand Down