Skip to content

Commit

Permalink
feat: Add Apache Pulsar module
Browse files Browse the repository at this point in the history
  • Loading branch information
David Jensen committed Feb 7, 2024
1 parent 1c80fe8 commit baaddb9
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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="DotPulsar" Version="3.1.2"/>
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="8.0.5"/>
<PackageVersion Include="EventStore.Client.Grpc.Streams" Version="22.0.0"/>
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="10.0.0"/>
Expand Down Expand Up @@ -59,4 +60,4 @@
<PackageVersion Include="Selenium.WebDriver" Version="4.8.1"/>
<PackageVersion Include="StackExchange.Redis" Version="2.6.90"/>
</ItemGroup>
</Project>
</Project>
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Pulsar", "src\Testcontainers.Pulsar\Testcontainers.Pulsar.csproj", "{27D46863-65B9-4934-B3C8-2383B217A477}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Pulsar.Tests", "tests\Testcontainers.Pulsar.Tests\Testcontainers.Pulsar.Tests.csproj", "{D05FCB31-793E-43E0-BD6C-077013AE9113}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -556,6 +560,14 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU
{27D46863-65B9-4934-B3C8-2383B217A477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27D46863-65B9-4934-B3C8-2383B217A477}.Debug|Any CPU.Build.0 = Debug|Any CPU
{27D46863-65B9-4934-B3C8-2383B217A477}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27D46863-65B9-4934-B3C8-2383B217A477}.Release|Any CPU.Build.0 = Release|Any CPU
{D05FCB31-793E-43E0-BD6C-077013AE9113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D05FCB31-793E-43E0-BD6C-077013AE9113}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D05FCB31-793E-43E0-BD6C-077013AE9113}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D05FCB31-793E-43E0-BD6C-077013AE9113}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -647,5 +659,7 @@ Global
{1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{27D46863-65B9-4934-B3C8-2383B217A477} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{D05FCB31-793E-43E0-BD6C-077013AE9113} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions src/Testcontainers.Pulsar/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
113 changes: 113 additions & 0 deletions src/Testcontainers.Pulsar/PulsarBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Text;
using Testcontainers.Pulsar;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class PulsarBuilder : ContainerBuilder<PulsarBuilder, PulsarContainer, PulsarConfiguration>
{
private const string AuthenticationPlugin = "org.apache.pulsar.client.impl.auth.AuthenticationToken";
private const string SecretKeyPath = "/pulsar/secret.key";
private const string UserName = "test-user";
private const string PulsarImage = "apachepulsar/pulsar:3.0.2";
private const string AdminClustersEndpoint = "/admin/v2/clusters";
internal const string Enabled = "Enabled";

private Dictionary<string, string> _environmentVariables = new Dictionary<string, string>
{
{ "PULSAR_PREFIX_tokenSecretKey", $"file://{SecretKeyPath}" },
{ "PULSAR_PREFIX_authenticationRefreshCheckSeconds", "5" },
{ "superUserRoles", UserName },
{ "authenticationEnabled", "true" },
{ "authorizationEnabled", "true" },
{ "authenticationProviders", "org.apache.pulsar.broker.authentication.AuthenticationProviderToken" },
{ "authenticateOriginalAuthData", "false" },
{ "brokerClientAuthenticationPlugin", AuthenticationPlugin },
{ "CLIENT_PREFIX_authPlugin", AuthenticationPlugin }
};

public const ushort PulsarBrokerPort = 6650;
public const ushort PulsarBrokerHttpPort = 8080;

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

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

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

/// <inheritdoc />
public override PulsarContainer Build()
{
Validate();
var pulsarStartupCommands = String.Empty;
if (DockerResourceConfiguration.Authentication == Enabled)
{
pulsarStartupCommands = $"bin/pulsar tokens create-secret-key --output {SecretKeyPath} && " +
$"export brokerClientAuthenticationParameters=token:$(bin/pulsar tokens create --secret-key {SecretKeyPath} --subject {UserName}) && " +
$"export CLIENT_PREFIX_authParams=$brokerClientAuthenticationParameters && bin/apply-config-from-env.py conf/standalone.conf && " +
$"bin/apply-config-from-env-with-prefix.py CLIENT_PREFIX_ conf/client.conf && ";
}
pulsarStartupCommands += "bin/pulsar standalone";

if (DockerResourceConfiguration.Functions != Enabled)
pulsarStartupCommands += " --no-functions-worker";

var pulsarBuilder = WithCommand("/bin/bash", "-c",pulsarStartupCommands);
return new PulsarContainer(pulsarBuilder.DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override PulsarBuilder Init()
{
return base.Init()
.WithImage(PulsarImage)
.WithPortBinding(PulsarBrokerPort, true)
.WithPortBinding(PulsarBrokerHttpPort, true)
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilCommandIsCompleted(["/bin/bash", "-c", "bin/pulsar-admin clusters list"]));
}

public PulsarBuilder WithTokenAuthentication()
{
return Merge(DockerResourceConfiguration, new PulsarConfiguration(authentication: Enabled))
.WithEnvironment(_environmentVariables);
}

public PulsarBuilder WithFunctions()
{
return Merge(DockerResourceConfiguration, new PulsarConfiguration(functions: Enabled));
}

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

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

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

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class PulsarConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="PulsarConfiguration" /> class.
/// </summary>
public PulsarConfiguration(string authentication = null,

Check warning on line 10 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Cannot convert null literal to non-nullable reference type.

Check warning on line 10 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Cannot convert null literal to non-nullable reference type.

Check warning on line 10 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Cannot convert null literal to non-nullable reference type.

Check warning on line 10 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Cannot convert null literal to non-nullable reference type.
string functions = null)

Check warning on line 11 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Cannot convert null literal to non-nullable reference type.

Check warning on line 11 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Cannot convert null literal to non-nullable reference type.

Check warning on line 11 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Cannot convert null literal to non-nullable reference type.

Check warning on line 11 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Cannot convert null literal to non-nullable reference type.
{
Authentication = authentication;
Functions = functions;
}

/// <summary>
/// Initializes a new instance of the <see cref="PulsarConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public PulsarConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Non-nullable property 'Authentication' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Non-nullable property 'Functions' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Non-nullable property 'Authentication' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Non-nullable property 'Functions' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Non-nullable property 'Authentication' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Non-nullable property 'Functions' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Non-nullable property 'Authentication' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 21 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Non-nullable property 'Functions' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
: 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="PulsarConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public PulsarConfiguration(IContainerConfiguration resourceConfiguration)

Check warning on line 31 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Non-nullable property 'Authentication' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 31 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

Non-nullable property 'Functions' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 31 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Non-nullable property 'Authentication' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 31 in src/Testcontainers.Pulsar/PulsarConfiguration.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Non-nullable property 'Functions' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
: 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="PulsarConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public PulsarConfiguration(PulsarConfiguration resourceConfiguration)
: this(new PulsarConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

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

/// <summary>
/// Gets authentication.
/// </summary>
public string Authentication { get; }

/// <summary>
/// Gets functions.
/// </summary>
public string Functions { get; }
}
58 changes: 58 additions & 0 deletions src/Testcontainers.Pulsar/PulsarContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
namespace Testcontainers.Pulsar;

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

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

/// <summary>
/// Gets the Pulsar broker url.
/// </summary>
/// <returns>The Pulsar broker url.</returns>
public string GetPulsarBrokerUrl() =>
new UriBuilder("pulsar://", Hostname, GetMappedPublicPort(PulsarBuilder.PulsarBrokerPort)).ToString();

/// <summary>
/// Gets the Pulsar service url.
/// </summary>
/// <returns>The Pulsar service url.</returns>
public string GetHttpServiceUrl() =>
new UriBuilder("http", Hostname, GetMappedPublicPort(PulsarBuilder.PulsarBrokerHttpPort)).ToString();

/// <summary>
/// Creates Authentication token
/// </summary>
/// <param name="expiryTime">Relative expiry time for the token (eg: 1h, 3d, 10y)</param>
/// <param name="cancellationToken"></param>
/// <returns>Authentication token</returns>
/// <exception cref="Exception"></exception>
public async Task<string> CreateToken(TimeSpan expiryTime, CancellationToken cancellationToken = default)
{
if (_configuration.Authentication != PulsarBuilder.Enabled)
throw new Exception($"Could not create the token, because WithAuthentication is not used.");

var arguments = $"bin/pulsar tokens create --secret-key /pulsar/secret.key --subject test-user";

if (expiryTime != Timeout.InfiniteTimeSpan)
arguments += $" --expiry-time {expiryTime.TotalSeconds}s";

var result = await ExecAsync(new[] { "/bin/bash", "-c", arguments }, cancellationToken);

if (result.ExitCode != 0)
throw new Exception($"Could not create the token: {result.Stderr}");

return result.Stdout;
}
}
14 changes: 14 additions & 0 deletions src/Testcontainers.Pulsar/Testcontainers.Pulsar.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TargetFrameworks>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>
8 changes: 8 additions & 0 deletions src/Testcontainers.Pulsar/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
global using System;
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;
Loading

0 comments on commit baaddb9

Please sign in to comment.