Skip to content

Commit

Permalink
Add Db2 module
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin0x90 committed Aug 20, 2024
1 parent 21137c6 commit 61bfd3d
Show file tree
Hide file tree
Showing 12 changed files with 534 additions and 96 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<PackageVersion Include="Confluent.Kafka" Version="2.0.2"/>
<PackageVersion Include="Consul" Version="1.6.10.9"/>
<PackageVersion Include="CouchbaseNetClient" Version="3.4.3"/>
<PackageVersion Include="Net.IBM.Data.Db2" Version="8.0.0.200" />
<PackageVersion Include="DotPulsar" Version="3.1.2"/>
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="8.0.5"/>
<PackageVersion Include="EventStore.Client.Grpc.Streams" Version="22.0.0"/>
Expand Down
209 changes: 113 additions & 96 deletions Testcontainers.sln

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/Testcontainers.Db2/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
191 changes: 191 additions & 0 deletions src/Testcontainers.Db2/Db2Builder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using System.Linq;
using System.Threading.Tasks;

namespace Testcontainers.Db2;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class Db2Builder : ContainerBuilder<Db2Builder, Db2Container, Db2Configuration>
{
public const string Db2Image = "icr.io/db2_community/db2:latest";

public const ushort Db2Port = 50000;

public const string DefaultDatabase = "test";

public const string DefaultUsername = "db2inst1";

public const string DefaultPassword = "test123";

public const string DefaultLicenseAgreement = "accept";

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

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

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

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

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

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

/// <summary>
/// Sets the Db2 archive logs.
/// </summary>
/// <param name="archiveLogs">The Db2 archive logs setting.</param>
/// <returns>A configured instance of <see cref="Db2Builder" />.</returns>
public Db2Builder WithArchiveLogs(bool archiveLogs)
{
return Merge(DockerResourceConfiguration, new Db2Configuration(archiveLogs: archiveLogs))
.WithEnvironment("ARCHIVE_LOGS", archiveLogs.ToString());
}

/// <summary>
/// Sets the Db2 autoconfig setting.
/// </summary>
/// <param name="autoConfig">The Db2 autoconfig setting.</param>
/// <returns>A configured instance of <see cref="Db2Builder" />.</returns>
public Db2Builder WithAutoconfig(bool autoConfig)
{
return Merge(DockerResourceConfiguration, new Db2Configuration(autoConfig: autoConfig))
.WithEnvironment("AUTOCONFIG", autoConfig.ToString());
}

/// <summary>
/// Accepts the Db2 license agreement.
/// </summary>
/// <returns>A configured instance of <see cref="Db2Builder" />.</returns>
public Db2Builder WithLicenseAgreement()
{
return Merge(DockerResourceConfiguration, new Db2Configuration(licenseAgreement: DefaultLicenseAgreement))
.WithEnvironment("LICENSE", DefaultLicenseAgreement);
}

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

// By default, the base builder waits until the container is running. However, for MySql, a more advanced waiting strategy is necessary that requires access to the configured database, username and password.
// If the user does not provide a custom waiting strategy, append the default MySql waiting strategy.
var db2Builder = DockerResourceConfiguration.WaitStrategies.Count() > 1
? this
: WithWaitStrategy(Wait.ForUnixContainer()
.UntilMessageIsLogged("All databases are now active")
.UntilMessageIsLogged("Setup has completed.")
.AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration))
);

return new Db2Container(db2Builder.DockerResourceConfiguration);
}

/// <inheritdoc />
protected override Db2Builder Init() => base.Init()
.WithImage(Db2Image)
.WithPortBinding(Db2Port, true)
.WithDatabase(DefaultDatabase)
.WithUsername(DefaultUsername)
.WithPassword(DefaultPassword)
.WithLicenseAgreement()
.WithPrivileged(true);

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

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

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

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

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

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

/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitUntil : IWaitUntil
{
/// <summary>
/// Initializes a new instance of the <see cref="WaitUntil" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public WaitUntil(Db2Configuration configuration)
{
}

/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var db2Container = (Db2Container)container;

var execResult = await db2Container.ExecScriptAsync("SELECT 1 FROM SYSIBM.SYSDUMMY1").ConfigureAwait(false);

return 0L.Equals(execResult.ExitCode);
}
}
}
96 changes: 96 additions & 0 deletions src/Testcontainers.Db2/Db2Configuration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
namespace Testcontainers.Db2;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class Db2Configuration : ContainerConfiguration
{

/// <summary>
/// Initializes a new instance of the <see cref="Db2Configuration" /> class.
/// </summary>
/// <param name="database">The Db2 database.</param>
/// <param name="username">The Db2 username.</param>
/// <param name="password">The Db2 password.</param>
/// <param name="archiveLogs">The Db2 archive logs setting.</param>
/// <param name="autoConfig">The Db2 auto config setting.</param>
/// <param name="licenseAgreement">The Db2 license agreement.</param>
public Db2Configuration(
string database = null,
string username = null,
string password = null,
bool archiveLogs = false,
bool autoConfig = false,
string licenseAgreement = null)
{
Database = database;
Username = username;
Password = password;
ArchiveLogs = archiveLogs;
AutoConfig = autoConfig;
LicenseAgreement = licenseAgreement;
}

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

/// <summary>
/// Initializes a new instance of the <see cref="Db2Configuration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public Db2Configuration(Db2Configuration oldValue, Db2Configuration 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 Db2 database.
/// </summary>
public string Database { get; }

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

/// <summary>
/// Gets the Db2 password.
/// </summary>
public string Password { get; }

public bool ArchiveLogs { get; }

public bool AutoConfig { get; }

public string LicenseAgreement { get; }
}
42 changes: 42 additions & 0 deletions src/Testcontainers.Db2/Db2Container.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Testcontainers.Db2
{
/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class Db2Container : DockerContainer, IDatabaseContainer
{
private static string Db2CommandPath = "/opt/ibm/db2/*/bin/db2";

private readonly Db2Configuration _configuration;

private const char ConnectionStringDelimiter = ';';

public Db2Container(Db2Configuration configuration) : base(configuration)
{
_configuration = configuration;
}

public string GetConnectionString() => new StringBuilder()
.Append("Server=").Append(Hostname).Append(":").Append(GetMappedPublicPort(Db2Builder.Db2Port).ToString()).Append(ConnectionStringDelimiter)
.Append("Database=").Append(_configuration.Database).Append(ConnectionStringDelimiter)
.Append("UID=").Append(_configuration.Username).Append(ConnectionStringDelimiter)
.Append("PWD=").Append(_configuration.Password).Append(ConnectionStringDelimiter)
.ToString();

public async Task<ExecResult> ExecScriptAsync(string scriptContent, CancellationToken cancellationToken = default)
{
string[] command = ["su", "db2inst1", "-c", new StringBuilder()
.Append(Db2CommandPath).Append(" connect to ").Append(_configuration.Database)
.Append(" user ").Append(_configuration.Username).Append(" using ").Append(_configuration.Password)
.Append("; ")
.Append(Db2CommandPath).Append(" ").Append(scriptContent)
.ToString()
];

return await ExecAsync(command).ConfigureAwait(false);
}
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.Db2/Testcontainers.Db2.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net8.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>
7 changes: 7 additions & 0 deletions src/Testcontainers.Db2/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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 System;
1 change: 1 addition & 0 deletions tests/Testcontainers.Db2.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Loading

0 comments on commit 61bfd3d

Please sign in to comment.