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 EventHubs module #1373

Merged
merged 1 commit into from
Feb 16, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
{ name: "Testcontainers.DynamoDb", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Elasticsearch", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.EventStoreDb", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.EventHubs", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.FakeGcsServer", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.FirebirdSql", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Firestore", 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 @@ -32,6 +32,7 @@
<PackageVersion Include="AWSSDK.SimpleNotificationService" Version="3.7.101.7"/>
<PackageVersion Include="AWSSDK.SQS" Version="3.7.100.71"/>
<PackageVersion Include="Azure.Data.Tables" Version="12.8.0"/>
<PackageVersion Include="Azure.Messaging.EventHubs" Version="5.11.3"/>
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.2"/>
<PackageVersion Include="Azure.Storage.Blobs" Version="12.17.0"/>
<PackageVersion Include="Azure.Storage.Queues" Version="12.15.0"/>
Expand Down
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearch", "src\Testcontainers.Elasticsearch\Testcontainers.Elasticsearch.csproj", "{641DDEA5-B6E0-41E6-BA11-7A28C0913127}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventHubs", "src\Testcontainers.EventHubs\Testcontainers.EventHubs.csproj", "{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb", "src\Testcontainers.EventStoreDb\Testcontainers.EventStoreDb.csproj", "{84D707E0-C9FA-4327-85DC-0AFEBEA73572}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FakeGcsServer", "src\Testcontainers.FakeGcsServer\Testcontainers.FakeGcsServer.csproj", "{FF86B509-2F9E-4269-ABC2-912B3339DE29}"
Expand Down Expand Up @@ -143,6 +145,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearch.Tests", "tests\Testcontainers.Elasticsearch.Tests\Testcontainers.Elasticsearch.Tests.csproj", "{DD5B3678-468F-4D73-AECE-705E3D66CD43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventHubs.Tests", "tests\Testcontainers.EventHubs.Tests\Testcontainers.EventHubs.Tests.csproj", "{4A0C5523-CEB2-49C9-AE62-9187A01B016B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb.Tests", "tests\Testcontainers.EventStoreDb.Tests\Testcontainers.EventStoreDb.Tests.csproj", "{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FakeGcsServer.Tests", "tests\Testcontainers.FakeGcsServer.Tests\Testcontainers.FakeGcsServer.Tests.csproj", "{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA}"
Expand Down Expand Up @@ -282,6 +286,10 @@ Global
{641DDEA5-B6E0-41E6-BA11-7A28C0913127}.Debug|Any CPU.Build.0 = Debug|Any CPU
{641DDEA5-B6E0-41E6-BA11-7A28C0913127}.Release|Any CPU.ActiveCfg = Release|Any CPU
{641DDEA5-B6E0-41E6-BA11-7A28C0913127}.Release|Any CPU.Build.0 = Release|Any CPU
{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Release|Any CPU.Build.0 = Release|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -486,6 +494,10 @@ Global
{DD5B3678-468F-4D73-AECE-705E3D66CD43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD5B3678-468F-4D73-AECE-705E3D66CD43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD5B3678-468F-4D73-AECE-705E3D66CD43}.Release|Any CPU.Build.0 = Release|Any CPU
{4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Release|Any CPU.Build.0 = Release|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -650,6 +662,7 @@ Global
{ED3C611F-DFE2-4AB7-A323-B500E95B4FF9} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{641DDEA5-B6E0-41E6-BA11-7A28C0913127} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{84D707E0-C9FA-4327-85DC-0AFEBEA73572} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{FF86B509-2F9E-4269-ABC2-912B3339DE29} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -701,6 +714,7 @@ Global
{AF9853AB-86E7-49DE-8DF8-454838E90D6F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{101515E6-74C1-40F9-85C8-871F742A378D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DD5B3678-468F-4D73-AECE-705E3D66CD43} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{4A0C5523-CEB2-49C9-AE62-9187A01B016B} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E39095AC-9B34-4178-A486-04C902B6FD33} = {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 @@ -24,6 +24,7 @@ await moduleNameContainer.StartAsync();
| ActiveMQ Artemis | `apache/activemq-artemis:2.31.2` | [NuGet](https://www.nuget.org/packages/Testcontainers.ActiveMq) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ActiveMq) |
| ArangoDB | `arangodb:3.11.5` | [NuGet](https://www.nuget.org/packages/Testcontainers.ArangoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ArangoDb) |
| Azure Cosmos DB | `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.CosmosDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.CosmosDb) |
| Azure Event Hubs | `mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.EventHubs) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.EventHubs) |
| Azure Service Bus | `mcr.microsoft.com/azure-messaging/servicebus-emulator:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.ServiceBus) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ServiceBus) |
| Azurite | `mcr.microsoft.com/azure-storage/azurite:3.24.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Azurite) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Azurite) |
| 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) |
Expand Down
15 changes: 6 additions & 9 deletions src/Testcontainers.Azurite/AzuriteContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,27 @@ public string GetConnectionString()
/// <summary>
/// Gets the blob endpoint
/// </summary>
/// <returns>The azurite blob endpoint</returns>
/// <returns>The Azurite blob endpoint</returns>
public string GetBlobEndpoint()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.BlobPort),
AzuriteBuilder.AccountName).ToString();
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.BlobPort), AzuriteBuilder.AccountName).ToString();
}

/// <summary>
/// Gets the queue endpoint
/// </summary>
/// <returns>The azurite queue endpoint</returns>
/// <returns>The Azurite queue endpoint</returns>
public string GetQueueEndpoint()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.QueuePort),
AzuriteBuilder.AccountName).ToString();
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.QueuePort), AzuriteBuilder.AccountName).ToString();
}

/// <summary>
/// Gets the table endpoint
/// </summary>
/// <returns>The azurite table endpoint</returns>
/// <returns>The Azurite table endpoint</returns>
public string GetTableEndpoint()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.TablePort),
AzuriteBuilder.AccountName).ToString();
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.TablePort), AzuriteBuilder.AccountName).ToString();
}
}
1 change: 1 addition & 0 deletions src/Testcontainers.EventHubs/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
178 changes: 178 additions & 0 deletions src/Testcontainers.EventHubs/EventHubsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
namespace Testcontainers.EventHubs;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class EventHubsBuilder : ContainerBuilder<EventHubsBuilder, EventHubsContainer, EventHubsConfiguration>
{
public const string EventHubsNetworkAlias = "eventhubs-container";

public const string AzuriteNetworkAlias = "azurite-container";

public const string EventHubsImage = "mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest";

public const ushort EventHubsPort = 5672;

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

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

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

/// <inheritdoc />
protected override string AcceptLicenseAgreementEnvVar { get; } = "ACCEPT_EULA";

/// <inheritdoc />
protected override string AcceptLicenseAgreement { get; } = "Y";

/// <inheritdoc />
protected override string DeclineLicenseAgreement { get; } = "N";

/// <summary>
/// Accepts the license agreement.
/// </summary>
/// <remarks>
/// When <paramref name="acceptLicenseAgreement" /> is set to <c>true</c>, the Azure Event Hubs Emulator <see href="https://github.com/Azure/azure-event-hubs-emulator-installer/blob/main/EMULATOR_EULA.md">license</see> is accepted.
/// </remarks>
/// <param name="acceptLicenseAgreement">A boolean value indicating whether the Azure Event Hubs Emulator license agreement is accepted.</param>
/// <returns>A configured instance of <see cref="EventHubsBuilder" />.</returns>
public override EventHubsBuilder WithAcceptLicenseAgreement(bool acceptLicenseAgreement)
{
var licenseAgreement = acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement;
return WithEnvironment(AcceptLicenseAgreementEnvVar, licenseAgreement);
}

/// <summary>
/// Sets the dependent Azurite container for the Azure Event Hubs Emulator.
/// </summary>
/// <remarks>
/// This method allows an existing Azurite container to be attached to the Azure Event
/// Hubs Emulator. The containers must be on the same network to enable communication
/// between them.
/// </remarks>
/// <param name="network">The network to connect the container to.</param>
/// <param name="container">The Azurite container.</param>
/// <param name="networkAlias">The Azurite container network alias.</param>
/// <returns>A configured instance of <see cref="EventHubsBuilder" />.</returns>
public EventHubsBuilder WithAzuriteContainer(
INetwork network,
AzuriteContainer container,
string networkAlias)
{
return Merge(DockerResourceConfiguration, new EventHubsConfiguration(azuriteContainer: container))
.DependsOn(container)
.WithNetwork(network)
.WithNetworkAliases(EventHubsNetworkAlias)
.WithEnvironment("BLOB_SERVER", networkAlias)
.WithEnvironment("METADATA_SERVER", networkAlias);
}

/// <summary>
/// Sets the Azure Event Hubs Emulator configuration.
/// </summary>
/// <param name="serviceConfiguration">The service configuration.</param>
/// <returns>A configured instance of <see cref="EventHubsBuilder" />.</returns>
public EventHubsBuilder WithConfigurationBuilder(EventHubsServiceConfiguration serviceConfiguration)
{
var resourceContent = Encoding.Default.GetBytes(serviceConfiguration.Build());
return Merge(DockerResourceConfiguration, new EventHubsConfiguration(serviceConfiguration: serviceConfiguration))
.WithResourceMapping(resourceContent, "Eventhubs_Emulator/ConfigFiles/Config.json");
}

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

if (DockerResourceConfiguration.AzuriteContainer != null)
{
return new EventHubsContainer(DockerResourceConfiguration);
}

// If the user has not provided an existing Azurite container instance,
// we configure one.
var network = new NetworkBuilder()
.Build();

var container = new AzuriteBuilder()
.WithNetwork(network)
.WithNetworkAliases(AzuriteNetworkAlias)
.Build();

var eventHubsContainer = WithAzuriteContainer(network, container, AzuriteNetworkAlias);
return new EventHubsContainer(eventHubsContainer.DockerResourceConfiguration);
}

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

_ = Guard.Argument(DockerResourceConfiguration.ServiceConfiguration, nameof(DockerResourceConfiguration.ServiceConfiguration))
.NotNull()
.ThrowIf(argument => !argument.Value.Validate(), _ => throw new ArgumentException("The service configuration of the Azure Event Hubs Emulator is invalid."));
}

/// <inheritdoc />
protected override EventHubsBuilder Init()
{
return base.Init()
.WithImage(EventHubsImage)
.WithPortBinding(EventHubsPort, true)
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilMessageIsLogged("Emulator Service is Successfully Up!")
.AddCustomWaitStrategy(new WaitTwoSeconds()));
}

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

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

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

/// <inheritdoc cref="IWaitUntil" />
/// <remarks>
/// This is a workaround to ensure that the wait strategy does not indicate
/// readiness too early:
/// https://github.com/Azure/azure-service-bus-emulator-installer/issues/35#issuecomment-2497164533.
/// </remarks>
private sealed class WaitTwoSeconds : IWaitUntil
{
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
await Task.Delay(TimeSpan.FromSeconds(2))
.ConfigureAwait(false);

return true;
}
}
}
70 changes: 70 additions & 0 deletions src/Testcontainers.EventHubs/EventHubsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Testcontainers.EventHubs;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class EventHubsConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="EventHubsConfiguration" /> class.
/// </summary>
/// <param name="azuriteContainer">The Azurite container.</param>
/// <param name="serviceConfiguration">The service configuration.</param>
public EventHubsConfiguration(AzuriteContainer azuriteContainer = null,
EventHubsServiceConfiguration serviceConfiguration = null)
{
AzuriteContainer = azuriteContainer;
ServiceConfiguration = serviceConfiguration;
}

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

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

/// <summary>
/// Gets the Azurite container.
/// </summary>
public AzuriteContainer AzuriteContainer { get; }

/// <summary>
/// Gets the service configuration.
/// </summary>
public EventHubsServiceConfiguration ServiceConfiguration { get; }
}
Loading
Loading