Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
{ name: "Testcontainers.Milvus", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Minio", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.MongoDb", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Mosquitto", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.MsSql", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.MySql", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Nats", runs-on: "ubuntu-22.04" },
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<PackageVersion Include="MongoDB.Driver" Version="3.2.0"/>
<PackageVersion Include="MyCouch" Version="7.6.0"/>
<PackageVersion Include="MySqlConnector" Version="2.2.5"/>
<PackageVersion Include="MQTTnet" Version="5.0.1.1416"/>
<PackageVersion Include="NATS.Client" Version="1.0.8"/>
<PackageVersion Include="Neo4j.Driver" Version="5.5.0"/>
<PackageVersion Include="Net.IBM.Data.Db2-lnx" Version="9.0.0.100"/>
Expand Down
1 change: 1 addition & 0 deletions Testcontainers.dic
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ lipsum
ltsc
memopt
mongosh
mosquitto
mycounter
mydatabase
myregistry
Expand Down
6 changes: 6 additions & 0 deletions Testcontainers.lutconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<LUTConfig Version="1.0">
<Repository />
<ParallelBuilds>true</ParallelBuilds>
<ParallelTestRuns>true</ParallelTestRuns>
<TestCaseTimeout>180000</TestCaseTimeout>
</LUTConfig>
17 changes: 17 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Xunit.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.XunitV3.Tests", "tests\Testcontainers.XunitV3.Tests\Testcontainers.XunitV3.Tests.csproj", "{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mosquitto", "src\Testcontainers.Mosquitto\Testcontainers.Mosquitto.csproj", "{3A64B210-645C-4229-B089-5BB2AAFCF535}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mosquitto.Tests", "tests\Testcontainers.Mosquitto.Tests\Testcontainers.Mosquitto.Tests.csproj", "{6314B57A-EE0C-4C3B-A9A9-64D68A47312A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -751,6 +755,14 @@ Global
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.Build.0 = Release|Any CPU
{3A64B210-645C-4229-B089-5BB2AAFCF535}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A64B210-645C-4229-B089-5BB2AAFCF535}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A64B210-645C-4229-B089-5BB2AAFCF535}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A64B210-645C-4229-B089-5BB2AAFCF535}.Release|Any CPU.Build.0 = Release|Any CPU
{6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -878,5 +890,10 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E901DF14-6F05-4FC2-825A-3055FAD33561} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{3A64B210-645C-4229-B089-5BB2AAFCF535} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{6314B57A-EE0C-4C3B-A9A9-64D68A47312A} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {06AF4E8B-EB32-4C33-B1DD-923580E132D5}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ await moduleNameContainer.StartAsync();
| Milvus | `milvusdb/milvus:v2.3.10` | [NuGet](https://www.nuget.org/packages/Testcontainers.Milvus) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Milvus) |
| 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) |
| Mosquitto | `eclipse-mosquitto:2.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Mosquitto) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Mosquitto) |
| 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) |
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.Mosquitto/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
127 changes: 127 additions & 0 deletions src/Testcontainers.Mosquitto/MosquittoBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
namespace TestContainers.Mosquitto;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public class MosquittoBuilder : ContainerBuilder<MosquittoBuilder, MosquittoContainer, MosquittoConfiguration>
{
public const string MosquittoImage = "eclipse-mosquitto:2.0";

public const int TcpPort = 1883;
public const int TlsPort = 8883;
public const int WsPort = 80;
public const int WssPort = 443;
public const string CertificateFilePath = "/mosquitto/certs/server.pem";
public const string CertificateKeyFilePath = "/mosquitto/certs/server-key.pem";

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

public MosquittoBuilder(MosquittoConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

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

/// <inheritdoc />
public override MosquittoContainer Build()
{
Validate();

var sb = new StringBuilder();
sb.AppendUnixLine("per_listener_settings true");

sb.AppendUnixLine();
sb.AppendUnixLine("# MQTT listener");
sb.AppendUnixLine($"listener {TcpPort}");
sb.AppendUnixLine("protocol mqtt");
sb.AppendUnixLine("allow_anonymous true");

sb.AppendUnixLine();
sb.AppendUnixLine("# WebSocket listener");
sb.AppendUnixLine($"listener {WsPort}");
sb.AppendUnixLine("protocol websockets");
sb.AppendUnixLine("allow_anonymous true");

if (DockerResourceConfiguration.HasCertificate)
{
sb.AppendUnixLine();
sb.AppendUnixLine("# MQTT listener (encrypted)");
sb.AppendUnixLine($"listener {TlsPort}");
sb.AppendUnixLine("protocol mqtt");
sb.AppendUnixLine("allow_anonymous true");
sb.AppendUnixLine($"certfile {CertificateFilePath}");
sb.AppendUnixLine($"keyfile {CertificateKeyFilePath}");

sb.AppendUnixLine();
sb.AppendUnixLine("# WebSocket listener (encrypted)");
sb.AppendUnixLine($"listener {WssPort}");
sb.AppendUnixLine("protocol websockets");
sb.AppendUnixLine("allow_anonymous true");
sb.AppendUnixLine($"certfile {CertificateFilePath}");
sb.AppendUnixLine($"keyfile {CertificateKeyFilePath}");
}

var config = Clone(DockerResourceConfiguration)
.WithResourceMapping(Encoding.UTF8.GetBytes(sb.ToString()), "/mosquitto/config/mosquitto.conf");

return new MosquittoContainer(config.DockerResourceConfiguration);
}


public MosquittoBuilder WithCertificate(string certificate, string certificateKey)
{
return Merge(DockerResourceConfiguration, new MosquittoConfiguration(certificate: certificate, certificateKey: certificateKey))
.WithPortBinding(TlsPort, true)
.WithPortBinding(WssPort, true)
.WithResourceMapping(Encoding.UTF8.GetBytes(certificate), CertificateFilePath)
.WithResourceMapping(Encoding.UTF8.GetBytes(certificateKey), CertificateKeyFilePath);
}

/// <inheritdoc />
protected override MosquittoBuilder Init()
{
var builder = base.Init()
.WithImage(MosquittoImage)
.WithPortBinding(TcpPort, true)
.WithPortBinding(WsPort, true)
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(@"mosquitto.*running"));

return builder;
}

/// <inheritdoc />
protected override void Validate()
{
base.Validate();

_ = Guard.Argument(DockerResourceConfiguration, "Certificate")
.ThrowIf(argument => 1.Equals(new[] { argument.Value.Certificate, argument.Value.CertificateKey }.Count(string.IsNullOrWhiteSpace)), argument => new ArgumentException($"Both {nameof(argument.Value.Certificate)} and {nameof(argument.Value.CertificateKey)} must be supplied if one is.", argument.Name));
}

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

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

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

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class MosquittoConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="MosquittoConfiguration" /> class.
/// </summary>
public MosquittoConfiguration(
string certificate = null,
string certificateKey = null)
{
Certificate = certificate;
CertificateKey = certificateKey;
}

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

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

}

/// <summary>
/// Gets the public certificate in PEM format.
/// </summary>
public string Certificate { get; }

/// <summary>
/// Gets the private key associated with the certificate in PEM format.
/// </summary>
public string CertificateKey { get; }

/// <summary>
/// Gets a value indicating whether both the certificate and the certificate key are provided.
/// </summary>
public bool HasCertificate => !string.IsNullOrWhiteSpace(Certificate) && !string.IsNullOrWhiteSpace(CertificateKey);
}
84 changes: 84 additions & 0 deletions src/Testcontainers.Mosquitto/MosquittoContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace TestContainers.Mosquitto;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class MosquittoContainer : DockerContainer
{
private readonly bool _isSecure;

/// <summary>
/// Initializes a new instance of the <see cref="MosquittoContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public MosquittoContainer(MosquittoConfiguration configuration)
: base(configuration)
{
_isSecure = configuration.HasCertificate;
}

/// <summary>
/// Gets the MQTT endpoint.
/// </summary>
/// <returns>A TCP address in the format: <c>tcp://hostname:port</c>.</returns>
public string GetEndpoint()
{
return new UriBuilder(Uri.UriSchemeNetTcp, Hostname, GetPort()).ToString();
}

/// <summary>
/// Gets the MQTT endpoint port.
/// </summary>
/// <returns>Exposed port for insecure MQQT connections.</returns>
public ushort GetPort()
{
return GetMappedPublicPort(MosquittoBuilder.TcpPort);
}

/// <summary>
/// Gets the secure MQTT endpoint.
/// </summary>
/// <returns>A TCP address in the format: <c>tcp://hostname:port</c>.</returns>
public string GetSecureEndpoint()
{
ThrowIfNotSecure();
return new UriBuilder(Uri.UriSchemeNetTcp, Hostname, GetMappedPublicPort(MosquittoBuilder.TlsPort)).ToString();
}

/// <summary>
/// Gets the secure MQTT endpoint port.
/// </summary>
/// <returns>Exposed port for secure MQTT connections.</returns>
public ushort GetSecurePort()
{
return GetMappedPublicPort(MosquittoBuilder.TlsPort);
}

/// <summary>
/// Gets the WebSocket endpoint.
/// </summary>
/// <returns>A WS address in the format: <c>ws://hostname:port</c>.</returns>
public string GetWsEndpoint()
{
return new UriBuilder("ws", Hostname, GetMappedPublicPort(MosquittoBuilder.WsPort)).ToString();
}

/// <summary>
/// Gets the secure WebSocket endpoint.
/// </summary>
/// <returns>A WS address in the format: <c>ws://hostname:port</c>.</returns>
public string GetWssEndpoint()
{
ThrowIfNotSecure();
return new UriBuilder("wss", Hostname, GetMappedPublicPort(MosquittoBuilder.WssPort)).ToString();
}

private void ThrowIfNotSecure()
{
if (_isSecure)
{
return;
}

throw new InvalidOperationException("The container was not configured with TLS/SSL support.");
}
}
11 changes: 11 additions & 0 deletions src/Testcontainers.Mosquitto/StringBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TestContainers.Mosquitto;

internal static class StringBuilderExtensions
{
public static StringBuilder AppendUnixLine(this StringBuilder sb, string value = "")
{
return sb
.Append(value)
.Append('\n');
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.Mosquitto/Testcontainers.Mosquitto.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" VersionOverride="2023.3.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Testcontainers/Testcontainers.csproj" />
</ItemGroup>
</Project>
Loading
Loading