Skip to content

Commit 698193e

Browse files
authored
feat: Add connection string provider (#1588)
1 parent 8aa6520 commit 698193e

File tree

17 files changed

+367
-4
lines changed

17 files changed

+367
-4
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Connection String Provider
2+
3+
The Connection String Provider API provides a standardized way to access and manage connection information for Testcontainers (modules). It allows developers to customize module-provided connection strings or add their own, and to access module-specific connection strings or endpoints (e.g., database connection strings, HTTP API base addresses) in a uniform way.
4+
5+
!!!note
6+
7+
Testcontainers modules do not yet implement this feature. Developers can use the provider to define and manage their own connection strings or endpoints. Providers will be integrated by modules in future releases.
8+
9+
## Example
10+
11+
Register a custom connection string provider via the container builder:
12+
13+
```csharp
14+
IContainer container = new ContainerBuilder()
15+
.WithConnectionStringProvider(new MyProvider1())
16+
.Build();
17+
18+
// Implicit host connection string (default)
19+
var hostConnectionStringImplicit = container.GetConnectionString();
20+
21+
// Explicit host connection string
22+
var hostConnectionStringExplicit = container.GetConnectionString(ConnectionMode.Host);
23+
24+
// Container-to-container connection string
25+
var containerConnectionString = container.GetConnectionString(ConnectionMode.Container);
26+
```
27+
28+
## Implementing a custom provider
29+
30+
To create a custom provider, implement the generic interface: `IConnectionStringProvider<TContainer, TConfiguration>`. The `Configure(TContainer, TConfiguration)` method is invoked after the container has successfully started, ensuring that all runtime-assigned values are available.
31+
32+
=== "Generic builder"
33+
```csharp
34+
public sealed class MyProvider1 : IConnectionStringProvider<IContainer, IContainerConfiguration>
35+
{
36+
public void Configure(IContainer container, IContainerConfiguration configuration)
37+
{
38+
// Initialize provider with container information.
39+
}
40+
41+
public string GetConnectionString(ConnectionMode connectionMode = ConnectionMode.Host)
42+
{
43+
// This method returns a default connection string. The connection mode argument
44+
// lets you choose between a host connection or a container-to-container connection.
45+
return "...";
46+
}
47+
48+
public string GetConnectionString(string name, ConnectionMode connectionMode = ConnectionMode.Host)
49+
{
50+
// This method returns a connection string for the given name. Useful for modules
51+
// with multiple endpoints (e.g., Azurite blob, queue, or table).
52+
return "...";
53+
}
54+
}
55+
```
56+
57+
=== "Module builder"
58+
```csharp
59+
public sealed class MyProvider2 : IConnectionStringProvider<PostgreSqlContainer, PostgreSqlConfiguration>
60+
{
61+
private string _host;
62+
63+
private ushort _port;
64+
65+
public void Configure(PostgreSqlContainer container, PostgreSqlConfiguration configuration)
66+
{
67+
// Initialize provider with container information.
68+
_host = container.Hostname;
69+
_port = container.GetMappedPublicPort(PostgreSqlBuilder.PostgreSqlPort);
70+
}
71+
72+
public string GetConnectionString(ConnectionMode connectionMode = ConnectionMode.Host)
73+
{
74+
return $"Host={_host};Port={_port};...";
75+
}
76+
77+
public string GetConnectionString(string name, ConnectionMode connectionMode = ConnectionMode.Host)
78+
{
79+
return $"Host={_host};Port={_port};...;SSL Mode=Require";
80+
}
81+
}
82+
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ nav:
4040
- api/resource_reaper.md
4141
- api/resource_reuse.md
4242
- api/wait_strategies.md
43+
- api/connection_string_provider.md
4344
- api/best_practices.md
4445
- dind/index.md
4546
- full_framework/index.md

src/Testcontainers.Couchbase/CouchbaseBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ private CouchbaseBuilder WithBucket(params CouchbaseBucket[] bucket)
179179
/// <summary>
180180
/// Configures the Couchbase node.
181181
/// </summary>
182-
/// <param name="container">The container.</param>
182+
/// <param name="container">The Couchbase container.</param>
183183
/// <param name="ct">Cancellation token.</param>
184184
private async Task ConfigureCouchbaseAsync(IContainer container, CancellationToken ct = default)
185185
{

src/Testcontainers/Builders/ContainerBuilder`3.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,12 @@ public TBuilderEntity WithStartupCallback(Func<TContainerEntity, TConfigurationE
396396
return Clone(new ContainerConfiguration(startupCallback: (container, configuration, ct) => startupCallback((TContainerEntity)container, (TConfigurationEntity)configuration, ct)));
397397
}
398398

399+
/// <inheritdoc />
400+
public TBuilderEntity WithConnectionStringProvider(IConnectionStringProvider<TContainerEntity, TConfigurationEntity> connectionStringProvider)
401+
{
402+
return Clone(new ContainerConfiguration(connectionStringProvider: new ConnectionStringProvider<TContainerEntity, TConfigurationEntity>(connectionStringProvider)));
403+
}
404+
399405
/// <inheritdoc />
400406
protected override TBuilderEntity Init()
401407
{

src/Testcontainers/Builders/IContainerBuilder`2.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ namespace DotNet.Testcontainers.Builders
2121
/// <typeparam name="TConfigurationEntity">The configuration entity.</typeparam>
2222
[PublicAPI]
2323
public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity, out TConfigurationEntity> : IAbstractBuilder<TBuilderEntity, TContainerEntity, CreateContainerParameters>
24+
where TContainerEntity : IContainer
25+
where TConfigurationEntity : IContainerConfiguration
2426
{
2527
/// <summary>
2628
/// Accepts the license agreement.
@@ -500,5 +502,13 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity, out
500502
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
501503
[PublicAPI]
502504
TBuilderEntity WithStartupCallback(Func<TContainerEntity, TConfigurationEntity, CancellationToken, Task> startupCallback);
505+
506+
/// <summary>
507+
/// Sets the connection string provider.
508+
/// </summary>
509+
/// <param name="connectionStringProvider">The connection string provider.</param>
510+
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
511+
[PublicAPI]
512+
TBuilderEntity WithConnectionStringProvider(IConnectionStringProvider<TContainerEntity, TConfigurationEntity> connectionStringProvider);
503513
}
504514
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace DotNet.Testcontainers.Configurations
2+
{
3+
using JetBrains.Annotations;
4+
5+
/// <summary>
6+
/// Represents the connection mode.
7+
/// </summary>
8+
[PublicAPI]
9+
public enum ConnectionMode
10+
{
11+
/// <summary>
12+
/// The connection string for the container.
13+
/// </summary>
14+
Container,
15+
16+
/// <summary>
17+
/// The connection string for the host.
18+
/// </summary>
19+
Host,
20+
}
21+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace DotNet.Testcontainers.Configurations
2+
{
3+
using DotNet.Testcontainers.Containers;
4+
5+
/// <inheritdoc cref="IConnectionStringProvider{TContainerEntity, TConfigurationEntity}" />
6+
internal sealed class ConnectionStringProvider<TContainerEntity, TConfigurationEntity> : IConnectionStringProvider<IContainer, IContainerConfiguration>
7+
where TContainerEntity : IContainer
8+
where TConfigurationEntity : IContainerConfiguration
9+
{
10+
private readonly IConnectionStringProvider<TContainerEntity, TConfigurationEntity> _connectionStringProvider;
11+
12+
/// <summary>
13+
/// Initializes a new instance of the <see cref="ConnectionStringProvider{TContainerEntity, TConfigurationEntity}" /> class.
14+
/// </summary>
15+
/// <param name="connectionStringProvider">The connection string provider.</param>
16+
public ConnectionStringProvider(IConnectionStringProvider<TContainerEntity, TConfigurationEntity> connectionStringProvider)
17+
{
18+
_connectionStringProvider = connectionStringProvider;
19+
}
20+
21+
/// <inheritdoc />
22+
public void Configure(IContainer container, IContainerConfiguration configuration)
23+
{
24+
_connectionStringProvider.Configure((TContainerEntity)container, (TConfigurationEntity)configuration);
25+
}
26+
27+
/// <inheritdoc />
28+
public string GetConnectionString(ConnectionMode connectionMode = ConnectionMode.Host)
29+
{
30+
return _connectionStringProvider.GetConnectionString(connectionMode);
31+
}
32+
33+
/// <inheritdoc />
34+
public string GetConnectionString(string name, ConnectionMode connectionMode = ConnectionMode.Host)
35+
{
36+
return _connectionStringProvider.GetConnectionString(name, connectionMode);
37+
}
38+
}
39+
}

src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public ContainerConfiguration(
6262
IOutputConsumer outputConsumer = null,
6363
IEnumerable<WaitStrategy> waitStrategies = null,
6464
Func<IContainer, IContainerConfiguration, CancellationToken, Task> startupCallback = null,
65+
IConnectionStringProvider<IContainer, IContainerConfiguration> connectionStringProvider = null,
6566
bool? autoRemove = null,
6667
bool? privileged = null)
6768
{
@@ -87,6 +88,7 @@ public ContainerConfiguration(
8788
OutputConsumer = outputConsumer;
8889
WaitStrategies = waitStrategies;
8990
StartupCallback = startupCallback;
91+
ConnectionStringProvider = connectionStringProvider;
9092
}
9193

9294
/// <summary>
@@ -135,6 +137,7 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
135137
OutputConsumer = BuildConfiguration.Combine(oldValue.OutputConsumer, newValue.OutputConsumer);
136138
WaitStrategies = BuildConfiguration.Combine<IEnumerable<WaitStrategy>>(oldValue.WaitStrategies, newValue.WaitStrategies);
137139
StartupCallback = BuildConfiguration.Combine(oldValue.StartupCallback, newValue.StartupCallback);
140+
ConnectionStringProvider = BuildConfiguration.Combine(oldValue.ConnectionStringProvider, newValue.ConnectionStringProvider);
138141
AutoRemove = (oldValue.AutoRemove.HasValue && oldValue.AutoRemove.Value) || (newValue.AutoRemove.HasValue && newValue.AutoRemove.Value);
139142
Privileged = (oldValue.Privileged.HasValue && oldValue.Privileged.Value) || (newValue.Privileged.HasValue && newValue.Privileged.Value);
140143
}
@@ -217,5 +220,9 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
217220
/// <inheritdoc />
218221
[JsonIgnore]
219222
public Func<IContainer, IContainerConfiguration, CancellationToken, Task> StartupCallback { get; }
223+
224+
/// <inheritdoc />
225+
[JsonIgnore]
226+
public IConnectionStringProvider<IContainer, IContainerConfiguration> ConnectionStringProvider { get; }
220227
}
221228
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace DotNet.Testcontainers.Configurations
2+
{
3+
using DotNet.Testcontainers.Containers;
4+
using JetBrains.Annotations;
5+
6+
/// <summary>
7+
/// A connection string provider.
8+
/// </summary>
9+
[PublicAPI]
10+
public interface IConnectionStringProvider
11+
{
12+
/// <summary>
13+
/// Gets the connection string.
14+
/// </summary>
15+
/// <param name="connectionMode">The connection mode.</param>
16+
/// <returns>The connection string.</returns>
17+
/// <exception cref="ConnectionStringProviderNotConfiguredException">Thrown when the connection string provider is not configured.</exception>
18+
[NotNull]
19+
string GetConnectionString(ConnectionMode connectionMode = ConnectionMode.Host);
20+
21+
/// <summary>
22+
/// Gets the connection string.
23+
/// </summary>
24+
/// <param name="name">The connection string name.</param>
25+
/// <param name="connectionMode">The connection mode.</param>
26+
/// <returns>The connection string.</returns>
27+
/// <exception cref="ConnectionStringProviderNotConfiguredException">Thrown when the connection string provider is not configured.</exception>
28+
[NotNull]
29+
string GetConnectionString(string name, ConnectionMode connectionMode = ConnectionMode.Host);
30+
}
31+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace DotNet.Testcontainers.Configurations
2+
{
3+
using DotNet.Testcontainers.Containers;
4+
using JetBrains.Annotations;
5+
6+
/// <inheritdoc cref="IConnectionStringProvider" />
7+
[PublicAPI]
8+
public interface IConnectionStringProvider<in TContainerEntity, in TConfigurationEntity> : IConnectionStringProvider
9+
where TContainerEntity : IContainer
10+
where TConfigurationEntity : IContainerConfiguration
11+
{
12+
/// <summary>
13+
/// Configures the connection string provider.
14+
/// </summary>
15+
/// <param name="container">The container instance.</param>
16+
/// <param name="configuration">The container configuration.</param>
17+
void Configure(TContainerEntity container, TConfigurationEntity configuration);
18+
}
19+
}

0 commit comments

Comments
 (0)