Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add NATS module #1003

Merged
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MsSql", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MySql", "src\Testcontainers.MySql\Testcontainers.MySql.csproj", "{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Nats", "src\Testcontainers.Nats\Testcontainers.Nats.csproj", "{BF37BEA1-0816-4326-B1E0-E82290F8FCE0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j", "src\Testcontainers.Neo4j\Testcontainers.Neo4j.csproj", "{ADC2372B-6FE0-421D-8277-BB628E8EFC22}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle", "src\Testcontainers.Oracle\Testcontainers.Oracle.csproj", "{596EAFC1-0496-495C-B382-D57415FA456A}"
Expand Down Expand Up @@ -115,6 +117,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MsSql.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MySql.Tests", "tests\Testcontainers.MySql.Tests\Testcontainers.MySql.Tests.csproj", "{E42DA1CE-698F-4E45-8D1F-5D5895893840}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Nats.Tests", "tests\Testcontainers.Nats.Tests\Testcontainers.Nats.Tests.csproj", "{87A3F137-6DC3-4CE5-91E6-01797D076086}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j.Tests", "tests\Testcontainers.Neo4j.Tests\Testcontainers.Neo4j.Tests.csproj", "{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle.Tests", "tests\Testcontainers.Oracle.Tests\Testcontainers.Oracle.Tests.csproj", "{4AC1088B-9965-4497-AC8E-570F1AD5631F}"
Expand Down Expand Up @@ -230,6 +234,10 @@ Global
{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC}.Release|Any CPU.Build.0 = Release|Any CPU
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0}.Release|Any CPU.Build.0 = Release|Any CPU
{ADC2372B-6FE0-421D-8277-BB628E8EFC22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADC2372B-6FE0-421D-8277-BB628E8EFC22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADC2372B-6FE0-421D-8277-BB628E8EFC22}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -354,6 +362,10 @@ Global
{E42DA1CE-698F-4E45-8D1F-5D5895893840}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E42DA1CE-698F-4E45-8D1F-5D5895893840}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E42DA1CE-698F-4E45-8D1F-5D5895893840}.Release|Any CPU.Build.0 = Release|Any CPU
{87A3F137-6DC3-4CE5-91E6-01797D076086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87A3F137-6DC3-4CE5-91E6-01797D076086}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87A3F137-6DC3-4CE5-91E6-01797D076086}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87A3F137-6DC3-4CE5-91E6-01797D076086}.Release|Any CPU.Build.0 = Release|Any CPU
{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -428,6 +440,7 @@ Global
{2613F146-6C66-4059-9D37-D48BA6B61515} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{121FB123-40D9-44D4-9AB7-AD57ED34F466} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{ADC2372B-6FE0-421D-8277-BB628E8EFC22} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{596EAFC1-0496-495C-B382-D57415FA456A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{8AB91636-9055-4900-A72A-7CFFACDFDBF0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -459,6 +472,7 @@ Global
{82A7E7B8-3187-4CAE-845B-0BF43409B38A} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{25DBED78-99F4-433F-BBF5-1B4E9DEAE437} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E42DA1CE-698F-4E45-8D1F-5D5895893840} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{87A3F137-6DC3-4CE5-91E6-01797D076086} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{4AC1088B-9965-4497-AC8E-570F1AD5631F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
Expand Down
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ await moduleNameContainer.StartAsync();
| MinIO | `minio/minio:RELEASE.2023-01-31T02-24-19Z` | [NuGet](https://www.nuget.org/packages/Testcontainers.Minio) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Minio) |
| MongoDB | `mongo:6.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MongoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MongoDb) |
| MySQL | `mysql:8.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MySql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MySql) |
| NATS | `nats:2.9` | [NuGet](https://www.nuget.org/packages/Testcontainers.Nats) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Nats) |
| Neo4j | `neo4j:5.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.Neo4j) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Neo4j) |
| Oracle | `gvenzl/oracle-xe:21.3.0-slim-faststart` | [NuGet](https://www.nuget.org/packages/Testcontainers.Oracle) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Oracle) |
| PostgreSQL | `postgres:15.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.PostgreSql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PostgreSql) |
Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers.MongoDb/MongoDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected override void Validate()
.NotNull();

_ = Guard.Argument(DockerResourceConfiguration, "Credentials")
.ThrowIf(argument => 1.Equals(new[] { argument.Value.Username, argument.Value.Password }.Count(string.IsNullOrEmpty)), argument => new ArgumentException(message, argument.Name));
.ThrowIf(argument => 1.Equals(new[] { argument.Value.Username, argument.Value.Password }.Count(string.IsNullOrWhiteSpace)), argument => new ArgumentException(message, argument.Name));
}

/// <inheritdoc />
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.Nats/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
117 changes: 117 additions & 0 deletions src/Testcontainers.Nats/NatsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
namespace Testcontainers.Nats;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class NatsBuilder : ContainerBuilder<NatsBuilder, NatsContainer, NatsConfiguration>
{
public const string NatsImage = "nats:2.9";

public const ushort NatsClientPort = 4222;

public const ushort NatsClusterRoutingPort = 6222;

public const ushort NatsHttpManagementPort = 8222;

/// <summary>
/// Initializes a new instance of the <see cref="NatsBuilder" /> class.
/// </summary>
public NatsBuilder()
: this(new NatsConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="NatsBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private NatsBuilder(NatsConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override NatsConfiguration DockerResourceConfiguration { get; }

/// <summary>
/// Sets the Nats username.
/// </summary>
/// <param name="username">The Nats username.</param>
/// <returns>A configured instance of <see cref="NatsBuilder" />.</returns>
public NatsBuilder WithUsername(string username)
{
return Merge(DockerResourceConfiguration, new NatsConfiguration(username: username))
.WithCommand("--user", username);
}

/// <summary>
/// Sets the Nats password.
/// </summary>
/// <param name="password">The Nats password.</param>
/// <returns>A configured instance of <see cref="NatsBuilder" />.</returns>
public NatsBuilder WithPassword(string password)
{
return Merge(DockerResourceConfiguration, new NatsConfiguration(password: password))
.WithCommand("--pass", password);
}

/// <inheritdoc />
public override NatsContainer Build()
{
Validate();
return new NatsContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override NatsBuilder Init()
{
return base.Init()
.WithImage(NatsImage)
.WithPortBinding(NatsClientPort, true)
.WithPortBinding(NatsHttpManagementPort, true)
.WithPortBinding(NatsClusterRoutingPort, true)
.WithUsername(string.Empty)
.WithPassword(string.Empty)
.WithCommand("--http_port", NatsHttpManagementPort.ToString())
.WithCommand("--jetstream")
.WithCommand("--debug")
.WithCommand("--trace")
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Server is ready"));
}

/// <inheritdoc />
protected override void Validate()
{
const string message = "Missing username or password. Both must be specified.";

base.Validate();

_ = Guard.Argument(DockerResourceConfiguration.Username, nameof(DockerResourceConfiguration.Username))
.NotNull();

_ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password))
.NotNull();

_ = Guard.Argument(DockerResourceConfiguration, "Credentials")
.ThrowIf(argument => 1.Equals(new[] { argument.Value.Username, argument.Value.Password }.Count(string.IsNullOrWhiteSpace)), argument => new ArgumentException(message, argument.Name));
}

/// <inheritdoc />
protected override NatsBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new NatsConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override NatsBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new NatsConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override NatsBuilder Merge(NatsConfiguration oldValue, NatsConfiguration newValue)
{
return new NatsBuilder(new NatsConfiguration(oldValue, newValue));
}
}
71 changes: 71 additions & 0 deletions src/Testcontainers.Nats/NatsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace Testcontainers.Nats;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class NatsConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="NatsConfiguration" /> class.
/// </summary>
/// <param name="username">The Nats username.</param>
/// <param name="password">The Nats password.</param>
public NatsConfiguration(
string username = null,
string password = null)
{
Username = username;
Password = password;
}

/// <summary>
/// Initializes a new instance of the <see cref="NatsConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public NatsConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="NatsConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public NatsConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="NatsConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public NatsConfiguration(NatsConfiguration resourceConfiguration)
: this(new NatsConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="NatsConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public NatsConfiguration(NatsConfiguration oldValue, NatsConfiguration newValue)
: base(oldValue, newValue)
{
Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username);
Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password);
}

/// <summary>
/// The Nats username.
/// </summary>
public string Username { get; }

/// <summary>
/// The Nats password.
/// </summary>
public string Password { get; }
}
43 changes: 43 additions & 0 deletions src/Testcontainers.Nats/NatsContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Testcontainers.Nats;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class NatsContainer : DockerContainer
{
private readonly NatsConfiguration _configuration;

/// <summary>
/// Initializes a new instance of the <see cref="NatsContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
/// <param name="logger">The logger.</param>
public NatsContainer(NatsConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
_configuration = configuration;
}

/// <summary>
/// Gets the Nats connection string.
/// </summary>
/// <remarks>
/// If both username and password are set in the builder configuration, they will be included in the connection string.
/// </remarks>
/// <returns>A Nats connection string in the format: <c>nats://hostname:port</c>.</returns>
public string GetConnectionString()
{
var endpoint = new UriBuilder("nats://", Hostname, GetMappedPublicPort(NatsBuilder.NatsClientPort));
endpoint.UserName = Uri.EscapeDataString(_configuration.Username);
endpoint.Password = Uri.EscapeDataString(_configuration.Password);
return endpoint.ToString();
}

/// <summary>
/// Gets the Nats monitoring endpoint.
/// </summary>
/// <returns>An HTTP address in the format: <c>http://hostname:port</c>.</returns>
public string GetManagementEndpoint()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(NatsBuilder.NatsHttpManagementPort)).ToString();
}
}
13 changes: 13 additions & 0 deletions src/Testcontainers.Nats/Testcontainers.Nats.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions src/Testcontainers.Nats/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
global using System;
global using System.Linq;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using JetBrains.Annotations;
global using Microsoft.Extensions.Logging;
1 change: 1 addition & 0 deletions tests/Testcontainers.Nats.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Loading