Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Link;
import com.github.dockerjava.api.model.LogConfig;
import com.github.dockerjava.api.model.PortBinding;
import com.github.dockerjava.api.model.Ports;
import com.github.dockerjava.api.model.Volume;
Expand Down Expand Up @@ -609,6 +610,21 @@ private HostConfig buildHostConfig(HostConfig config) {
if (tmpFsMapping != null) {
config.withTmpFs(tmpFsMapping);
}
TestcontainersConfiguration
.getInstance()
.getContainerLogDriver()
.ifPresent(driver -> {
LogConfig.LoggingType type = LogConfig.LoggingType.fromValue(driver);
if (type != null) {
config.withLogConfig(new LogConfig(type));
} else {
logger()
.warn(
"Container log driver '{}' is not recognized by the docker-java client library and cannot be applied, ignoring.",
driver
);
}
});
return config;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ public String getImagePullPolicy() {
return getEnvVarOrProperty("pull.policy", null);
}

public Optional<String> getContainerLogDriver() {
return Optional
.ofNullable(getEnvVarOrProperty("container.log.driver", null))
.map(String::trim)
.filter(s -> !s.isEmpty());
}

public Integer getClientPingTimeout() {
return Integer.parseInt(getEnvVarOrProperty("client.ping.timeout", "10"));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.testcontainers.containers;

import com.github.dockerjava.api.model.LogConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.TestImages;
import org.testcontainers.utility.MockTestcontainersConfigurationExtension;
import org.testcontainers.utility.TestcontainersConfiguration;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;

@ExtendWith(MockTestcontainersConfigurationExtension.class)
class ContainerLogDriverConfigurationTest {

private static String daemonDefault;

@BeforeAll
static void fetchDaemonDefault() {
daemonDefault = DockerClientFactory.instance().client().infoCmd().exec().getLoggingDriver();
}

@Test
void shouldApplyConfiguredLogDriverToContainer() {
LogConfig.LoggingType overrideDriver = daemonDefault.equals(LogConfig.LoggingType.NONE.getType())
? LogConfig.LoggingType.JSON_FILE
: LogConfig.LoggingType.NONE;

Mockito
.doReturn(Optional.of(overrideDriver.getType()))
.when(TestcontainersConfiguration.getInstance())
.getContainerLogDriver();

try (
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
.withCommand("tail", "-f", "/dev/null")
) {
container.start();

assertThat(container.getContainerInfo().getHostConfig().getLogConfig().getType().getType())
.as("container should use the configured log driver instead of the daemon default (%s)", daemonDefault)
.isEqualTo(overrideDriver.getType());
}
}

@Test
void shouldNotOverrideLogDriverWhenNotConfigured() {
Mockito.doReturn(Optional.empty()).when(TestcontainersConfiguration.getInstance()).getContainerLogDriver();

try (
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
.withCommand("tail", "-f", "/dev/null")
) {
container.start();

assertThat(container.getContainerInfo().getHostConfig().getLogConfig().getType().getType())
.as("container should use the daemon default log driver when none is configured")
.isEqualTo(daemonDefault);
}
}

@Test
void shouldAllowPerContainerOverrideOfGlobalLogDriver() {
// Set global config to the non-default driver
LogConfig.LoggingType globalDriver = daemonDefault.equals(LogConfig.LoggingType.NONE.getType())
? LogConfig.LoggingType.JSON_FILE
: LogConfig.LoggingType.NONE;
Mockito
.doReturn(Optional.of(globalDriver.getType()))
.when(TestcontainersConfiguration.getInstance())
.getContainerLogDriver();

// Pick a different driver to override with at the per-container level
LogConfig.LoggingType perContainerDriver = globalDriver == LogConfig.LoggingType.NONE
? LogConfig.LoggingType.JSON_FILE
: LogConfig.LoggingType.NONE;

try (
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
.withCreateContainerCmdModifier(cmd -> {
cmd.getHostConfig().withLogConfig(new LogConfig(perContainerDriver));
})
.withCommand("tail", "-f", "/dev/null")
) {
container.start();

assertThat(container.getContainerInfo().getHostConfig().getLogConfig().getType().getType())
.as("per-container modifier should override the global container.log.driver setting")
.isNotEqualTo(globalDriver.getType())
.isEqualTo(perContainerDriver.getType());
}
}

@Test
void shouldWarnAndIgnoreUnsupportedLogDriver() {
Mockito
.doReturn(Optional.of("invalid-driver-xyz"))
.when(TestcontainersConfiguration.getInstance())
.getContainerLogDriver();

try (
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
.withCommand("tail", "-f", "/dev/null")
) {
assertThatNoException().isThrownBy(container::start);

assertThat(container.getContainerInfo().getHostConfig().getLogConfig().getType().getType())
.as("container should fall back to the daemon default log driver when an invalid driver is configured")
.isEqualTo(daemonDefault);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Info;
import com.github.dockerjava.api.model.LogConfig;
import com.github.dockerjava.api.model.Ports;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -232,6 +233,28 @@ void testArchitectureCheck() {
}
}

@Test
void shouldApplyLogDriverFromHostConfig() {
String daemonDefault = DockerClientFactory.instance().client().infoCmd().exec().getLoggingDriver();
LogConfig.LoggingType overrideDriver = daemonDefault.equals(LogConfig.LoggingType.NONE.getType())
? LogConfig.LoggingType.JSON_FILE
: LogConfig.LoggingType.NONE;

try (
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
.withCreateContainerCmdModifier(cmd -> {
cmd.getHostConfig().withLogConfig(new LogConfig(overrideDriver));
})
.withCommand("tail", "-f", "/dev/null")
) {
container.start();

assertThat(container.getContainerInfo().getHostConfig().getLogConfig().getType().getType())
.as("container should use the configured log driver instead of the daemon default (%s)", daemonDefault)
.isEqualTo(overrideDriver.getType());
}
}

@Test
void shouldReturnTheProvidedImage() {
GenericContainer container = new GenericContainer(TestImages.REDIS_IMAGE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,35 @@ void shouldReadReuseFromEnvironment() {
assertThat(newConfig().environmentSupportsReuse()).as("reuse enabled via env var").isTrue();
}

@Test
void shouldReturnEmptyOptionalWhenContainerLogDriverNotSet() {
assertThat(newConfig().getContainerLogDriver()).as("no container log driver override by default").isEmpty();
}

@Test
void shouldReadContainerLogDriverFromClasspathProperties() {
classpathProperties.setProperty("container.log.driver", "json-file");
assertThat(newConfig().getContainerLogDriver())
.as("container log driver can be set via classpath properties")
.hasValue("json-file");
}

@Test
void shouldReadContainerLogDriverFromUserProperties() {
userProperties.setProperty("container.log.driver", "json-file");
assertThat(newConfig().getContainerLogDriver())
.as("container log driver can be set via user properties")
.hasValue("json-file");
}

@Test
void shouldReadContainerLogDriverFromEnvironmentVariable() {
environment.put("TESTCONTAINERS_CONTAINER_LOG_DRIVER", "json-file");
assertThat(newConfig().getContainerLogDriver())
.as("container log driver can be set via environment variable")
.hasValue("json-file");
}

@Test
void shouldTrimImageNames() {
userProperties.setProperty("ryuk.container.image", " testcontainers/ryuk:0.3.2 ");
Expand Down
18 changes: 17 additions & 1 deletion docs/features/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,30 @@ Some companies disallow the usage of Docker Hub, but you can override `*.image`
> In some environments ryuk must be started in privileged mode to work properly (--privileged flag)

### Disabling Ryuk
Ryuk must be started as a privileged container.
Ryuk must be started as a privileged container.
If your environment already implements automatic cleanup of containers after the execution,
but does not allow starting privileged containers, you can turn off the Ryuk container by setting
`TESTCONTAINERS_RYUK_DISABLED` **environment variable** to `true`.

!!!tip
Note that Testcontainers will continue doing the cleanup at JVM's shutdown, unless you `kill -9` your JVM process.

## Customizing container log driver

> **container.log.driver** (env var: `TESTCONTAINERS_CONTAINER_LOG_DRIVER`)
> Sets the default Docker log driver for all containers started by Testcontainers (e.g. `json-file`, `syslog`).
> Useful when the Docker daemon is configured with a default log driver (such as `none`) that is incompatible with log-based wait strategies.
> This default can still be overridden on a per-container basis using `withCreateContainerCmdModifier`, since modifiers run after the host config is built.
> Defaults to the Docker daemon's configured log driver if not set.
> The value must be recognized by the docker-java client library (e.g. `json-file`, `none`, `syslog`, `journald`).

!!! warning
This setting applies to **all** containers started by Testcontainers, including the Ryuk resource reaper.
Setting this to `none` will break Ryuk's log-based wait strategy.

!!! warning
Only log driver names recognized by the docker-java client library are supported. Unrecognized values are silently ignored with a warning.

## Customizing image pull behaviour

> **pull.timeout = 120**
Expand Down