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 CockroachDB module #1077

Merged
merged 9 commits into from
Jan 26, 2024
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
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Bigtable", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ClickHouse", "src\Testcontainers.ClickHouse\Testcontainers.ClickHouse.csproj", "{B061A78E-536E-4CA1-8401-234D5FBFBAB7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.CockroachDb", "src\Testcontainers.CockroachDb\Testcontainers.CockroachDb.csproj", "{8D9871C6-5A39-4F0B-A15A-E87D34F3EA73}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Consul", "src\Testcontainers.Consul\Testcontainers.Consul.csproj", "{51ED33B9-B688-401E-85F2-329D3C935BD1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.CosmosDb", "src\Testcontainers.CosmosDb\Testcontainers.CosmosDb.csproj", "{A724806F-8C94-4438-8011-04A9A1575318}"
Expand Down Expand Up @@ -107,6 +109,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Bigtable.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ClickHouse.Tests", "tests\Testcontainers.ClickHouse.Tests\Testcontainers.ClickHouse.Tests.csproj", "{9D0A0B32-4921-400C-99CB-8650677E3E44}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.CockroachDb.Tests", "tests\Testcontainers.CockroachDb.Tests\Testcontainers.CockroachDb.Tests.csproj", "{685E6D9A-B05E-41D9-A08E-5F3CA7733F7D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Commons", "tests\Testcontainers.Commons\Testcontainers.Commons.csproj", "{2478673C-B063-469D-ABD1-0C3E0A25541B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Consul.Tests", "tests\Testcontainers.Consul.Tests\Testcontainers.Consul.Tests.csproj", "{91B23679-A2A5-4132-8AFA-740CE40A88B6}"
Expand Down Expand Up @@ -220,6 +224,10 @@ Global
{B061A78E-536E-4CA1-8401-234D5FBFBAB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B061A78E-536E-4CA1-8401-234D5FBFBAB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B061A78E-536E-4CA1-8401-234D5FBFBAB7}.Release|Any CPU.Build.0 = Release|Any CPU
{8D9871C6-5A39-4F0B-A15A-E87D34F3EA73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D9871C6-5A39-4F0B-A15A-E87D34F3EA73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D9871C6-5A39-4F0B-A15A-E87D34F3EA73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D9871C6-5A39-4F0B-A15A-E87D34F3EA73}.Release|Any CPU.Build.0 = Release|Any CPU
{51ED33B9-B688-401E-85F2-329D3C935BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51ED33B9-B688-401E-85F2-329D3C935BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51ED33B9-B688-401E-85F2-329D3C935BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -384,6 +392,10 @@ Global
{9D0A0B32-4921-400C-99CB-8650677E3E44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D0A0B32-4921-400C-99CB-8650677E3E44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D0A0B32-4921-400C-99CB-8650677E3E44}.Release|Any CPU.Build.0 = Release|Any CPU
{685E6D9A-B05E-41D9-A08E-5F3CA7733F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{685E6D9A-B05E-41D9-A08E-5F3CA7733F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{685E6D9A-B05E-41D9-A08E-5F3CA7733F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{685E6D9A-B05E-41D9-A08E-5F3CA7733F7D}.Release|Any CPU.Build.0 = Release|Any CPU
{2478673C-B063-469D-ABD1-0C3E0A25541B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2478673C-B063-469D-ABD1-0C3E0A25541B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2478673C-B063-469D-ABD1-0C3E0A25541B}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -552,6 +564,7 @@ Global
{A9FF9C7F-BBA0-4B44-90B7-48A60F9E00F3} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{302EC1E0-AE75-4E99-A6BF-524F35338BC8} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{B061A78E-536E-4CA1-8401-234D5FBFBAB7} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{8D9871C6-5A39-4F0B-A15A-E87D34F3EA73} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{51ED33B9-B688-401E-85F2-329D3C935BD1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{A724806F-8C94-4438-8011-04A9A1575318} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{58E94721-2681-4D82-8D94-0B2F9DB0D575} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -593,6 +606,7 @@ Global
{03E60673-078A-4508-99AD-8537CE6F78F1} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{2E7B92E3-8526-4706-90F3-00F0F5C47C37} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{9D0A0B32-4921-400C-99CB-8650677E3E44} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{685E6D9A-B05E-41D9-A08E-5F3CA7733F7D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{2478673C-B063-469D-ABD1-0C3E0A25541B} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{91B23679-A2A5-4132-8AFA-740CE40A88B6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{BD445A54-F411-4758-955E-397A1E98680C} = {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 @@ -29,6 +29,7 @@ await moduleNameContainer.StartAsync();
| BigQuery | `ghcr.io/goccy/bigquery-emulator:0.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.BigQuery) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.BigQuery) |
| Bigtable | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.Bigtable) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Bigtable) |
| ClickHouse | `clickhouse/clickhouse-server:23.6-alpine` | [NuGet](https://www.nuget.org/packages/Testcontainers.ClickHouse) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ClickHouse) |
| CockroachDB | `cockroachdb:23.1.13` | [NuGet](https://www.nuget.org/packages/Testcontainers.CockroachDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.CockroachDb) |
| Consul | `consul:1.15` | [NuGet](https://www.nuget.org/packages/Testcontainers.Consul) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Consul) |
| Couchbase | `couchbase:community-7.0.2` | [NuGet](https://www.nuget.org/packages/Testcontainers.Couchbase) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Couchbase) |
| CouchDB | `couchdb:3.3` | [NuGet](https://www.nuget.org/packages/Testcontainers.CouchDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.CouchDb) |
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.CockroachDb/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
141 changes: 141 additions & 0 deletions src/Testcontainers.CockroachDb/CockroachDbBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
namespace Testcontainers.CockroachDb;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class CockroachDbBuilder : ContainerBuilder<CockroachDbBuilder, CockroachDbContainer, CockroachDbConfiguration>
{
public const string CockroachDbImage = "cockroachdb/cockroach:latest-v23.1";

public const ushort CockroachDbPort = 26257;

public const ushort CockroachDbRestPort = 8080;

public const string DefaultDatabase = "defaultdb";

public const string DefaultUsername = "root";

public const string DefaultPassword = "";

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

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

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

/// <summary>
/// Sets the CockroachDb database.
/// </summary>
/// <param name="database">The CockroachDb database.</param>
/// <returns>A configured instance of <see cref="CockroachDbBuilder" />.</returns>
public CockroachDbBuilder WithDatabase(string database)
{
return Merge(DockerResourceConfiguration, new CockroachDbConfiguration(database: database))
.WithEnvironment("COCKROACH_DATABASE", database);
}

/// <summary>
/// Sets the CockroachDb username.
/// </summary>
/// <param name="username">The CockroachDb username.</param>
/// <returns>A configured instance of <see cref="CockroachDbBuilder" />.</returns>
public CockroachDbBuilder WithUsername(string username)
{
return Merge(DockerResourceConfiguration, new CockroachDbConfiguration(username: username))
.WithEnvironment("COCKROACH_USER", username);
}

/// <summary>
/// Sets the CockroachDb password.
/// </summary>
/// <param name="password">The CockroachDb password.</param>
/// <returns>A configured instance of <see cref="CockroachDbBuilder" />.</returns>
public CockroachDbBuilder WithPassword(string password)
{
return Merge(DockerResourceConfiguration, new CockroachDbConfiguration(password: password))
.WithEnvironment("COCKROACH_PASSWORD", password);
}

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

/// <inheritdoc />
protected override CockroachDbBuilder Init()
{
return base.Init()
.WithImage(CockroachDbImage)
.WithPortBinding(CockroachDbPort, true)
.WithPortBinding(CockroachDbRestPort, true)
.WithDatabase(DefaultDatabase)
.WithUsername(DefaultUsername)
.WithPassword(DefaultPassword)
.WithCommand("start-single-node")
.WithCommand("--insecure")
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
request.ForPort(CockroachDbRestPort).ForPath("/health")));
}

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

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

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

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

/// <inheritdoc />
protected override CockroachDbBuilder Merge(CockroachDbConfiguration oldValue, CockroachDbConfiguration newValue)
{
return new CockroachDbBuilder(new CockroachDbConfiguration(oldValue, newValue));
}

/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitUntil : IWaitUntil
{
private static readonly string[] LineEndings = ["\r\n", "\n"];

/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, stderr) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return 2.Equals(Array.Empty<string>()
.Concat(stdout.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries))
.Concat(stderr.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries))
.Count(line => line.Contains("database system is ready to accept connections")));
}
}
}
80 changes: 80 additions & 0 deletions src/Testcontainers.CockroachDb/CockroachDbConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
namespace Testcontainers.CockroachDb;

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

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

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

/// <summary>
/// Gets the CockroachDb database.
/// </summary>
public string Database { get; }

/// <summary>
/// Gets the CockroachDb username.
/// </summary>
public string Username { get; }

/// <summary>
/// Gets the CockroachDb password.
/// </summary>
public string Password { get; }
}
51 changes: 51 additions & 0 deletions src/Testcontainers.CockroachDb/CockroachDbContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Testcontainers.CockroachDb;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class CockroachDbContainer : DockerContainer, IDatabaseContainer
{
private readonly CockroachDbConfiguration _configuration;

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

/// <summary>
/// Gets the CockroachDb connection string.
/// </summary>
/// <returns>The CockroachDb connection string.</returns>
public string GetConnectionString()
{
var properties = new Dictionary<string, string>();
properties.Add("Host", Hostname);
properties.Add("Port", GetMappedPublicPort(CockroachDbBuilder.CockroachDbPort).ToString());
properties.Add("Database", _configuration.Database);
properties.Add("Username", _configuration.Username);
properties.Add("Password", _configuration.Password);
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}

/// <summary>
/// Executes the SQL script in the CockroachDb container.
/// </summary>
/// <param name="scriptContent">The content of the SQL script to execute.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Task that completes when the SQL script has been executed.</returns>
public async Task<ExecResult> ExecScriptAsync(string scriptContent, CancellationToken ct = default)
{
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());

await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
.ConfigureAwait(false);

return await ExecAsync(new[] { "cockroach", "sql", "--insecure", "--file", scriptFilePath }, ct)
.ConfigureAwait(false);
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.CockroachDb/Testcontainers.CockroachDb.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions src/Testcontainers.CockroachDb/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Text;
global using System.Threading;
global using System.Threading.Tasks;
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;
2 changes: 1 addition & 1 deletion src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected override PostgreSqlBuilder Merge(PostgreSqlConfiguration oldValue, Pos
/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitUntil : IWaitUntil
{
private static readonly string[] LineEndings = { "\r\n", "\n" };
private static readonly string[] LineEndings = ["\r\n", "\n"];

/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
Expand Down
1 change: 1 addition & 0 deletions tests/Testcontainers.CockroachDb.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Loading