Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<PackageVersion Include="Confluent.SchemaRegistry.Serdes.Json" Version="2.8.0"/>
<PackageVersion Include="Confluent.SchemaRegistry" Version="2.8.0"/>
<PackageVersion Include="Consul" Version="1.6.10.9"/>
<PackageVersion Include="CouchbaseNetClient" Version="3.6.4"/>
<PackageVersion Include="CouchbaseNetClient" Version="3.7.2"/>
<PackageVersion Include="DotPulsar" Version="3.6.0"/>
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="8.16.3"/>
<PackageVersion Include="EventStore.Client.Grpc.Streams" Version="22.0.0"/>
Expand Down
37 changes: 19 additions & 18 deletions docs/custom_configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@

Testcontainers supports various configurations to set up your test environment. It automatically discovers the Docker environment and applies the configuration. You can set or override the default values either with the Testcontainers [properties file][properties-file-format] (`~/.testcontainers.properties`) or with environment variables. The following configurations are available:

| Properties File | Environment Variable | Description | Default |
|-----------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|-----------------------------|
| `docker.config` | `DOCKER_CONFIG` | The directory path that contains the Docker configuration (`config.json`) file. | `~/.docker/` |
| `docker.host` | `DOCKER_HOST` | The Docker daemon socket to connect to. | - |
| `docker.context` | `DOCKER_CONTEXT` | The Docker context to connect to. | - |
| `docker.auth.config` | `DOCKER_AUTH_CONFIG` | The Docker configuration file content (GitLab: [Use statically-defined credentials][use-statically-defined-credentials]). | - |
| `docker.cert.path` | `DOCKER_CERT_PATH` | The directory path that contains the client certificate (`{ca,cert,key}.pem`) files. | `~/.docker/` |
| `docker.tls` | `DOCKER_TLS` | Enables TLS. | `false` |
| `docker.tls.verify` | `DOCKER_TLS_VERIFY` | Enables TLS verify. | `false` |
| `host.override` | `TESTCONTAINERS_HOST_OVERRIDE` | The host that exposes Docker's ports. | - |
| `docker.socket.override` | `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | The file path to the Docker daemon socket that is used by Ryuk (resource reaper). | `/var/run/docker.sock` |
| `ryuk.disabled` | `TESTCONTAINERS_RYUK_DISABLED` | Disables Ryuk (resource reaper). | `false` |
| `ryuk.container.privileged` | `TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED` | Runs Ryuk (resource reaper) in privileged mode. | `true` |
| `ryuk.container.image` | `TESTCONTAINERS_RYUK_CONTAINER_IMAGE` | The Ryuk (resource reaper) Docker image. | `testcontainers/ryuk:0.5.1` |
| `hub.image.name.prefix` | `TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX` | The name to use for substituting the Docker Hub registry part of the image name. | - |
| `wait.strategy.retries` | `TESTCONTAINERS_WAIT_STRATEGY_RETRIES` | The wait strategy retry count. | `infinite` |
| `wait.strategy.interval` | `TESTCONTAINERS_WAIT_STRATEGY_INTERVAL` | The wait strategy interval<sup>1</sup>. | `00:00:01` |
| `wait.strategy.timeout` | `TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT` | The wait strategy timeout<sup>1</sup>. | `01:00:00` |
| Properties File | Environment Variable | Description | Default |
|---------------------------------|------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|------------------------------|
| `docker.config` | `DOCKER_CONFIG` | The directory path that contains the Docker configuration (`config.json`) file. | `~/.docker/` |
| `docker.host` | `DOCKER_HOST` | The Docker daemon socket to connect to. | - |
| `docker.context` | `DOCKER_CONTEXT` | The Docker context to connect to. | - |
| `docker.auth.config` | `DOCKER_AUTH_CONFIG` | The Docker configuration file content (GitLab: [Use statically-defined credentials][use-statically-defined-credentials]). | - |
| `docker.cert.path` | `DOCKER_CERT_PATH` | The directory path that contains the client certificate (`{ca,cert,key}.pem`) files. | `~/.docker/` |
| `docker.tls` | `DOCKER_TLS` | Enables TLS. | `false` |
| `docker.tls.verify` | `DOCKER_TLS_VERIFY` | Enables TLS verify. | `false` |
| `host.override` | `TESTCONTAINERS_HOST_OVERRIDE` | The host that exposes Docker's ports. | - |
| `docker.socket.override` | `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | The file path to the Docker daemon socket that is used by Ryuk (resource reaper). | `/var/run/docker.sock` |
| `ryuk.disabled` | `TESTCONTAINERS_RYUK_DISABLED` | Disables Ryuk (resource reaper). | `false` |
| `ryuk.container.privileged` | `TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED` | Runs Ryuk (resource reaper) in privileged mode. | `true` |
| `ryuk.container.image` | `TESTCONTAINERS_RYUK_CONTAINER_IMAGE` | The Ryuk (resource reaper) Docker image. | `testcontainers/ryuk:0.12.0` |
| `hub.image.name.prefix` | `TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX` | The name to use for substituting the Docker Hub registry part of the image name. | - |
| `wait.strategy.retries` | `TESTCONTAINERS_WAIT_STRATEGY_RETRIES` | The wait strategy retry count. | `infinite` |
| `wait.strategy.interval` | `TESTCONTAINERS_WAIT_STRATEGY_INTERVAL` | The wait strategy interval<sup>1</sup>. | `00:00:01` |
| `wait.strategy.timeout` | `TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT` | The wait strategy timeout<sup>1</sup>. | `01:00:00` |
| `named.pipe.connection.timeout` | `TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT` | The named pipe connection timeout<sup>1</sup>. | `00:00:01` |

1) The value represent the string representation of a [TimeSpan](https://learn.microsoft.com/en-us/dotnet/api/system.timespan), for example, `00:00:01` for 1 second.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,11 @@ public string GetHubImageNamePrefix()
{
return null;
}

/// <inheritdoc />
public TimeSpan? GetNamedPipeConnectionTimeout()
{
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ public string GetHubImageNamePrefix()
return _customConfiguration.GetWaitStrategyTimeout();
}

/// <inheritdoc />
public TimeSpan? GetNamedPipeConnectionTimeout()
{
return _customConfiguration.GetNamedPipeConnectionTimeout();
}

private sealed class TestcontainersConfiguration : PropertiesFileConfiguration
{
public TestcontainersConfiguration()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,40 @@ namespace DotNet.Testcontainers.Configurations
[PublicAPI]
public readonly struct DockerEndpointAuthenticationConfiguration : IDockerEndpointAuthenticationConfiguration
{
// Since the static `TestcontainersSettings` class holds the detected container
// runtime information from the auto-discovery mechanism, we can't add a static
// `NamedPipeConnectionTimeout` property to it because that would create a
// circular dependency during discovery. To fix this, we either need to split the
// class or stop exposing the `TestcontainersSettings` properties publicly.
// Instead, we could rely only on custom configurations via environment variables
// or the properties file.
private static readonly TimeSpan NamedPipeConnectionTimeout = EnvironmentConfiguration.Instance.GetNamedPipeConnectionTimeout() ?? PropertiesFileConfiguration.Instance.GetNamedPipeConnectionTimeout() ?? TimeSpan.FromSeconds(1);

/// <summary>
/// Initializes a new instance of the <see cref="DockerEndpointAuthenticationConfiguration" /> struct.
/// </summary>
/// <param name="endpoint">The Docker API endpoint.</param>
/// <param name="credentials">The Docker API authentication credentials.</param>
public DockerEndpointAuthenticationConfiguration(Uri endpoint, Credentials credentials = null)
{
Credentials = credentials;
Endpoint = endpoint;
Credentials = credentials;
}

/// <inheritdoc />
public Credentials Credentials { get; }
public Uri Endpoint { get; }

/// <inheritdoc />
public Uri Endpoint { get; }
public Credentials Credentials { get; }

/// <inheritdoc />
public DockerClientConfiguration GetDockerClientConfiguration(Guid sessionId = default)
{
var defaultHttpRequestHeaders = new Dictionary<string, string>();
defaultHttpRequestHeaders.Add("User-Agent", "tc-dotnet/" + TestcontainersClient.Version);
defaultHttpRequestHeaders.Add("x-tc-sid", sessionId.ToString("D"));
return new DockerClientConfiguration(Endpoint, Credentials, defaultHttpRequestHeaders: defaultHttpRequestHeaders);

return new DockerClientConfiguration(Endpoint, Credentials, namedPipeConnectTimeout: NamedPipeConnectionTimeout, defaultHttpRequestHeaders: defaultHttpRequestHeaders);
}
}
}
21 changes: 16 additions & 5 deletions src/Testcontainers/Configurations/CustomConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,17 @@ protected virtual string GetHubImageNamePrefix(string propertyName)

protected virtual TimeSpan? GetWaitStrategyInterval(string propertyName)
{
return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, CultureInfo.InvariantCulture, out var result) && result > TimeSpan.Zero ? result : null;
return GetPropertyValue<TimeSpan?>(propertyName);
}

protected virtual TimeSpan? GetWaitStrategyTimeout(string propertyName)
{
return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, CultureInfo.InvariantCulture, out var result) && result > TimeSpan.Zero ? result : null;
return GetPropertyValue<TimeSpan?>(propertyName);
}

protected virtual TimeSpan? GetNamedPipeConnectionTimeout(string propertyName)
{
return GetPropertyValue<TimeSpan?>(propertyName);
}

private T GetPropertyValue<T>(string propertyName)
Expand All @@ -129,21 +134,27 @@ private T GetPropertyValue<T>(string propertyName)

var isNullable = type != typeof(T);

var hasValue = _properties.TryGetValue(propertyName, out var propertyValue);

if (typeof(TimeSpan) == type)
{
return (T)(object)(hasValue && TimeSpan.TryParse(propertyValue, CultureInfo.InvariantCulture, out var result) && result > TimeSpan.Zero ? result : null);
}

switch (Type.GetTypeCode(type))
{
case TypeCode.Boolean:
{
return (T)(object)(_properties.TryGetValue(propertyName, out var propertyValue) && bool.TryParse(propertyValue, out var result) ? result : isNullable ? null : "1".Equals(propertyValue, StringComparison.Ordinal));
return (T)(object)(hasValue && bool.TryParse(propertyValue, out var result) ? result : isNullable ? null : "1".Equals(propertyValue, StringComparison.Ordinal));
}

case TypeCode.UInt16:
{
return (T)(object)(_properties.TryGetValue(propertyName, out var propertyValue) && ushort.TryParse(propertyValue, out var result) ? result : isNullable ? null : 0);
return (T)(object)(hasValue && ushort.TryParse(propertyValue, out var result) ? result : isNullable ? null : 0);
}

case TypeCode.String:
{
_ = _properties.TryGetValue(propertyName, out var propertyValue);
return (T)(object)(string.IsNullOrEmpty(propertyValue) ? null : propertyValue);
}

Expand Down
9 changes: 9 additions & 0 deletions src/Testcontainers/Configurations/EnvironmentConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ internal class EnvironmentConfiguration : CustomConfiguration, ICustomConfigurat

private const string WaitStrategyTimeout = "TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT";

private const string NamedPipeConnectionTimeout = "TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT";

static EnvironmentConfiguration()
{
}
Expand All @@ -68,6 +70,7 @@ public EnvironmentConfiguration()
WaitStrategyRetries,
WaitStrategyInterval,
WaitStrategyTimeout,
NamedPipeConnectionTimeout,
}
.ToDictionary(key => key, Environment.GetEnvironmentVariable))
{
Expand Down Expand Up @@ -174,5 +177,11 @@ public string GetHubImageNamePrefix()
{
return GetWaitStrategyTimeout(WaitStrategyTimeout);
}

/// <inheritdoc />
public TimeSpan? GetNamedPipeConnectionTimeout()
{
return GetWaitStrategyTimeout(NamedPipeConnectionTimeout);
}
}
}
8 changes: 8 additions & 0 deletions src/Testcontainers/Configurations/ICustomConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,13 @@ internal interface ICustomConfiguration
/// <remarks>https://dotnet.testcontainers.org/custom_configuration/.</remarks>
[CanBeNull]
TimeSpan? GetWaitStrategyTimeout();

/// <summary>
/// Gets the named pipe connection timeout custom configuration.
/// </summary>
/// <returns>The named pipe connection timeout custom configuration.</returns>
/// <remarks>https://dotnet.testcontainers.org/custom_configuration/.</remarks>
[CanBeNull]
TimeSpan? GetNamedPipeConnectionTimeout();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,12 @@ public string GetHubImageNamePrefix()
const string propertyName = "wait.strategy.timeout";
return GetWaitStrategyTimeout(propertyName);
}

/// <inheritdoc />
public TimeSpan? GetNamedPipeConnectionTimeout()
{
const string propertyName = "named.pipe.connection.timeout";
return GetWaitStrategyTimeout(propertyName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static EnvironmentConfigurationTest()
EnvironmentVariables.Add("TESTCONTAINERS_WAIT_STRATEGY_RETRIES");
EnvironmentVariables.Add("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL");
EnvironmentVariables.Add("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT");
EnvironmentVariables.Add("TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT");
}

[Theory]
Expand Down Expand Up @@ -227,6 +228,18 @@ public void GetWaitStrategyTimeoutCustomConfiguration(string propertyName, strin
Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout()?.ToString());
}

[Theory]
[InlineData("", "", null)]
[InlineData("TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT", "", null)]
[InlineData("TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT", "-00:00:00.001", null)]
[InlineData("TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT", "00:00:01", "00:00:01")]
public void GetNamedPipeConnectionTimeoutCustomConfiguration(string propertyName, string propertyValue, string expected)
{
SetEnvironmentVariable(propertyName, propertyValue);
ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
Assert.Equal(expected, customConfiguration.GetNamedPipeConnectionTimeout()?.ToString());
}

public void Dispose()
{
foreach (var propertyName in EnvironmentVariables)
Expand Down Expand Up @@ -423,6 +436,17 @@ public void GetWaitStrategyTimeoutCustomConfiguration(string configuration, stri
ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout()?.ToString());
}

[Theory]
[InlineData("", null)]
[InlineData("named.pipe.connection.timeout=", null)]
[InlineData("named.pipe.connection.timeout=-00:00:00.001", null)]
[InlineData("named.pipe.connection.timeout=00:00:01", "00:00:01")]
public void GetNamedPipeConnectionTimeoutCustomConfiguration(string configuration, string expected)
{
ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
Assert.Equal(expected, customConfiguration.GetNamedPipeConnectionTimeout()?.ToString());
}
}
}
}