Skip to content

Commit

Permalink
Merge branch 'main' into fix_image_kwargs
Browse files Browse the repository at this point in the history
  • Loading branch information
Tranquility2 authored Dec 10, 2024
2 parents 63ce587 + 3436cbf commit 1a891ce
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/.release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "4.8.2"
".": "4.9.0"
}
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ You need to have the following tools available to you:

## Adding new containers

We have an [issue template](.github/ISSUE_TEMPLATE/new-container.md) for adding new containers, please refer to that for more information.
We have an [issue template](./ISSUE_TEMPLATE/new-container.md) for adding new containers, please refer to that for more information.
Once you've talked to the maintainers (we do our best to reply!) then you can proceed with contributing the new container.

> [!WARNING]
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## [4.9.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.8.2...testcontainers-v4.9.0) (2024-11-26)


### Features

* **compose:** support for setting profiles ([#738](https://github.com/testcontainers/testcontainers-python/issues/738)) ([3e00e71](https://github.com/testcontainers/testcontainers-python/commit/3e00e71da4d2b5e7fd30315468d4e54c86ba6150))
* **core:** Support working with env files ([#737](https://github.com/testcontainers/testcontainers-python/issues/737)) ([932ee30](https://github.com/testcontainers/testcontainers-python/commit/932ee307955e3591a63f194aee8e2f6d8e2f6bf9))


### Bug Fixes

* allow running all tests ([#721](https://github.com/testcontainers/testcontainers-python/issues/721)) ([f958cf9](https://github.com/testcontainers/testcontainers-python/commit/f958cf9fe62a5f3ee2dc255713ec8b16de6a767d))
* **core:** Avoid hanging upon bad docker host connection ([#742](https://github.com/testcontainers/testcontainers-python/issues/742)) ([4ced198](https://github.com/testcontainers/testcontainers-python/commit/4ced1983162914fe511a6e714f136b670e1dbdfb))
* **core:** running testcontainer inside container ([#714](https://github.com/testcontainers/testcontainers-python/issues/714)) ([85a6666](https://github.com/testcontainers/testcontainers-python/commit/85a66667c23d76e87aecc6761bbb01429adb3dee))
* **generic:** Also catch URLError waiting for ServerContainer ([#743](https://github.com/testcontainers/testcontainers-python/issues/743)) ([24e354f](https://github.com/testcontainers/testcontainers-python/commit/24e354f3bfa5912eaf7877da9442a885d7872f1a))
* update wait_for_logs to not throw on 'created', and an optimization ([#719](https://github.com/testcontainers/testcontainers-python/issues/719)) ([271ca9a](https://github.com/testcontainers/testcontainers-python/commit/271ca9a0fef2e5f2b216457bfee44318e93990bf))
* Vault health check ([#734](https://github.com/testcontainers/testcontainers-python/issues/734)) ([79434d6](https://github.com/testcontainers/testcontainers-python/commit/79434d6744b2918493884cf8fbf27aeadf78ecfd))


### Documentation

* Documentation fix for ServerContainer ([#671](https://github.com/testcontainers/testcontainers-python/issues/671)) ([0303d47](https://github.com/testcontainers/testcontainers-python/commit/0303d47d7173e1c4ec1a4f565efee9b2fe694928))

## [4.8.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.8.1...testcontainers-v4.8.2) (2024-09-27)


Expand Down
3 changes: 3 additions & 0 deletions core/testcontainers/compose/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class DockerCompose:
env_file: Optional[str] = None
services: Optional[list[str]] = None
docker_command_path: Optional[str] = None
profiles: Optional[list[str]] = None

def __post_init__(self):
if isinstance(self.compose_file_name, str):
Expand Down Expand Up @@ -198,6 +199,8 @@ def compose_command_property(self) -> list[str]:
if self.compose_file_name:
for file in self.compose_file_name:
docker_compose_cmd += ["-f", file]
if self.profiles:
docker_compose_cmd += [item for profile in self.profiles for item in ["--profile", profile]]
if self.env_file:
docker_compose_cmd += ["--env-file", self.env_file]
return docker_compose_cmd
Expand Down
18 changes: 16 additions & 2 deletions core/testcontainers/core/container.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import contextlib
from os import PathLike
from socket import socket
from typing import TYPE_CHECKING, Optional, Union

import docker.errors
from docker import version
from docker.types import EndpointConfig
from dotenv import dotenv_values
from typing_extensions import Self, assert_never

from testcontainers.core.config import ConnectionMode
from testcontainers.core.config import testcontainers_config as c
from testcontainers.core.docker_client import DockerClient
from testcontainers.core.exceptions import ContainerStartException
from testcontainers.core.exceptions import ContainerConnectException, ContainerStartException
from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID
from testcontainers.core.network import Network
from testcontainers.core.utils import is_arm, setup_logger
Expand Down Expand Up @@ -57,6 +59,12 @@ def with_env(self, key: str, value: str) -> Self:
self.env[key] = value
return self

def with_env_file(self, env_file: Union[str, PathLike]) -> Self:
env_values = dotenv_values(env_file)
for key, value in env_values.items():
self.with_env(key, value)
return self

def with_bind_ports(self, container: int, host: Optional[int] = None) -> Self:
self.ports[container] = host
return self
Expand Down Expand Up @@ -220,15 +228,21 @@ def _create_instance(cls) -> "Reaper":
.with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout)
.start()
)
wait_for_logs(Reaper._container, r".* Started!")
wait_for_logs(Reaper._container, r".* Started!", timeout=20, raise_on_exit=True)

container_host = Reaper._container.get_container_host_ip()
container_port = int(Reaper._container.get_exposed_port(8080))

if not container_host or not container_port:
raise ContainerConnectException(
f"Could not obtain network details for {Reaper._container._container.id}. Host: {container_host} Port: {container_port}"
)

last_connection_exception: Optional[Exception] = None
for _ in range(50):
try:
Reaper._socket = socket()
Reaper._socket.settimeout(1)
Reaper._socket.connect((container_host, container_port))
last_connection_exception = None
break
Expand Down
4 changes: 4 additions & 0 deletions core/testcontainers/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class ContainerStartException(RuntimeError):
pass


class ContainerConnectException(RuntimeError):
pass


class ContainerIsNotRunning(RuntimeError):
pass

Expand Down
16 changes: 16 additions & 0 deletions core/tests/compose_fixtures/profile_support/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
runs-always: &simple-service
image: alpine:latest
init: true
command:
- sh
- -c
- 'while true; do sleep 0.1 ; date -Ins; done'
runs-profile-a:
<<: *simple-service
profiles:
- profile-a
runs-profile-b:
<<: *simple-service
profiles:
- profile-b
26 changes: 25 additions & 1 deletion core/tests/test_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pathlib import Path
from re import split
from time import sleep
from typing import Union
from typing import Union, Optional
from urllib.request import urlopen, Request

import pytest
Expand Down Expand Up @@ -352,3 +352,27 @@ def fetch(req: Union[Request, str]):
if 200 < res.getcode() >= 400:
raise Exception(f"HTTP Error: {res.getcode()} - {res.reason}: {body}")
return res.getcode(), body


@pytest.mark.parametrize(
argnames=["profiles", "running", "not_running"],
argvalues=[
pytest.param(None, ["runs-always"], ["runs-profile-a", "runs-profile-b"], id="default"),
pytest.param(
["profile-a"], ["runs-always", "runs-profile-a"], ["runs-profile-b"], id="one-additional-profile-via-str"
),
pytest.param(
["profile-a", "profile-b"],
["runs-always", "runs-profile-a", "runs-profile-b"],
[],
id="all-profiles-explicitly",
),
],
)
def test_compose_profile_support(profiles: Optional[list[str]], running: list[str], not_running: list[str]):
with DockerCompose(context=FIXTURES / "profile_support", profiles=profiles) as compose:
for service in running:
assert compose.get_container(service) is not None
for service in not_running:
with pytest.raises(ContainerIsNotRunning):
compose.get_container(service)
29 changes: 29 additions & 0 deletions core/tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import tempfile
from pathlib import Path

from testcontainers.core.container import DockerContainer


Expand All @@ -17,3 +20,29 @@ def test_get_logs():
assert isinstance(stdout, bytes)
assert isinstance(stderr, bytes)
assert "Hello from Docker".encode() in stdout, "There should be something on stdout"


def test_docker_container_with_env_file():
"""Test that environment variables can be loaded from a file"""
with tempfile.TemporaryDirectory() as temp_directory:
env_file_path = Path(temp_directory) / "env_file"
with open(env_file_path, "w") as f:
f.write(
"""
TEST_ENV_VAR=hello
NUMBER=123
DOMAIN=example.org
ADMIN_EMAIL=admin@${DOMAIN}
ROOT_URL=${DOMAIN}/app
"""
)
container = DockerContainer("alpine").with_command("tail -f /dev/null") # Keep the container running
container.with_env_file(env_file_path) # Load the environment variables from the file
with container:
output = container.exec("env").output.decode("utf-8").strip()
assert "TEST_ENV_VAR=hello" in output
assert "NUMBER=123" in output
assert "DOMAIN=example.org" in output
assert "[email protected]" in output
assert "ROOT_URL=example.org/app" in output
print(output)
8 changes: 4 additions & 4 deletions modules/generic/testcontainers/generic/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Union
from urllib.error import HTTPError
from urllib.error import HTTPError, URLError
from urllib.request import urlopen

import httpx
Expand Down Expand Up @@ -31,16 +31,16 @@ class ServerContainer(DockerContainer):
... delay = wait_for_logs(srv, "GET / HTTP/1.1")
:param path: Path to the Dockerfile to build the image
:param tag: Tag for the image to be built (default: None)
:param port: Port to be exposed on the container.
:param image: Docker image to be used for the container.
"""

def __init__(self, port: int, image: Union[str, DockerImage]) -> None:
super().__init__(str(image))
self.internal_port = port
self.with_exposed_ports(self.internal_port)

@wait_container_is_ready(HTTPError)
@wait_container_is_ready(HTTPError, URLError)
def _connect(self) -> None:
# noinspection HttpUrlsUsage
url = self._create_connection_url()
Expand Down
3 changes: 2 additions & 1 deletion modules/vault/testcontainers/vault/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# under the License.

from http.client import HTTPException
from urllib.error import URLError
from urllib.request import urlopen

from testcontainers.core.container import DockerContainer
Expand Down Expand Up @@ -61,7 +62,7 @@ def get_connection_url(self) -> str:
exposed_port = self.get_exposed_port(self.port)
return f"http://{host_ip}:{exposed_port}"

@wait_container_is_ready(HTTPException)
@wait_container_is_ready(HTTPException, URLError)
def _healthcheck(self) -> None:
url = f"{self.get_connection_url()}/v1/sys/health"
with urlopen(url) as res:
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "testcontainers"
version = "4.8.2" # auto-incremented by release-please
version = "4.9.0" # auto-incremented by release-please
description = "Python library for throwaway instances of anything that can run in a Docker container"
authors = ["Sergey Pirogov <[email protected]>"]
maintainers = [
Expand Down Expand Up @@ -83,6 +83,7 @@ docker = "*" # ">=4.0"
urllib3 = "*" # "<2.0"
wrapt = "*" # "^1.16.0"
typing-extensions = "*"
python-dotenv = "*"

# community modules
python-arango = { version = "^7.8", optional = true }
Expand Down

0 comments on commit 1a891ce

Please sign in to comment.