diff --git a/Testcontainers.sln b/Testcontainers.sln
index 813d4d211..6c664505d 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql", "src\Testcontainers.PostgreSql\Testcontainers.PostgreSql.csproj", "{8AB91636-9055-4900-A72A-7CFFACDFDBF0}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub", "src\Testcontainers.PubSub\Testcontainers.PubSub.csproj", "{E6642255-667D-476B-B584-089AA5E6C0B1}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RabbitMq", "src\Testcontainers.RabbitMq\Testcontainers.RabbitMq.csproj", "{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RavenDb", "src\Testcontainers.RavenDb\Testcontainers.RavenDb.csproj", "{F6394475-D6F1-46E2-81BF-4BA78A40B878}"
@@ -131,6 +133,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Win
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql.Tests", "tests\Testcontainers.PostgreSql.Tests\Testcontainers.PostgreSql.Tests.csproj", "{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub.Tests", "tests\Testcontainers.PubSub.Tests\Testcontainers.PubSub.Tests.csproj", "{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RabbitMq.Tests", "tests\Testcontainers.RabbitMq.Tests\Testcontainers.RabbitMq.Tests.csproj", "{19564567-1736-4626-B406-17E4E02F18B2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RavenDb.Tests", "tests\Testcontainers.RavenDb.Tests\Testcontainers.RavenDb.Tests.csproj", "{D53726B6-5447-47E6-B881-A44EFF6E5534}"
@@ -252,6 +256,10 @@ Global
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E6642255-667D-476B-B584-089AA5E6C0B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E6642255-667D-476B-B584-089AA5E6C0B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E6642255-667D-476B-B584-089AA5E6C0B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E6642255-667D-476B-B584-089AA5E6C0B1}.Release|Any CPU.Build.0 = Release|Any CPU
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -392,6 +400,10 @@ Global
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Release|Any CPU.Build.0 = Release|Any CPU
{19564567-1736-4626-B406-17E4E02F18B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19564567-1736-4626-B406-17E4E02F18B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19564567-1736-4626-B406-17E4E02F18B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -450,6 +462,7 @@ Global
{ADC2372B-6FE0-421D-8277-BB628E8EFC22} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{596EAFC1-0496-495C-B382-D57415FA456A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{8AB91636-9055-4900-A72A-7CFFACDFDBF0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {E6642255-667D-476B-B584-089AA5E6C0B1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{F6394475-D6F1-46E2-81BF-4BA78A40B878} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{BFDA179A-40EB-4CEB-B8E9-0DF32C65E2C5} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -485,6 +498,7 @@ Global
{DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{3E55CBE8-AFE8-426D-9470-49D63CD1051C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {0F86BCE8-62E1-4BFC-AA84-63C7514C90AC} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{19564567-1736-4626-B406-17E4E02F18B2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{D53726B6-5447-47E6-B881-A44EFF6E5534} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{31EE94A0-E721-4073-B6F1-DD912D004DEF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
diff --git a/src/Testcontainers.PubSub/.editorconfig b/src/Testcontainers.PubSub/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.PubSub/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.PubSub/PubSubBuilder.cs b/src/Testcontainers.PubSub/PubSubBuilder.cs
new file mode 100644
index 000000000..f9c14cef2
--- /dev/null
+++ b/src/Testcontainers.PubSub/PubSubBuilder.cs
@@ -0,0 +1,68 @@
+namespace Testcontainers.PubSub;
+
+///
+[PublicAPI]
+public sealed class PubSubBuilder : ContainerBuilder
+{
+ public const string GoogleCloudCliImage = "gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators";
+
+ public const ushort PubSubPort = 8085;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PubSubBuilder()
+ : this(new PubSubConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private PubSubBuilder(PubSubConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override PubSubConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ public override PubSubContainer Build()
+ {
+ Validate();
+ return new PubSubContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
+ }
+
+ ///
+ protected override PubSubBuilder Init()
+ {
+ return base.Init()
+ .WithImage(GoogleCloudCliImage)
+ .WithPortBinding(PubSubPort, true)
+ .WithEntrypoint("gcloud")
+ .WithCommand("beta", "emulators", "pubsub", "start", "--host-port", "0.0.0.0:" + PubSubPort)
+ .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("(?s).*started.*$"));
+ }
+
+ ///
+ protected override PubSubBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new PubSubConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override PubSubBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new PubSubConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override PubSubBuilder Merge(PubSubConfiguration oldValue, PubSubConfiguration newValue)
+ {
+ return new PubSubBuilder(new PubSubConfiguration(oldValue, newValue));
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.PubSub/PubSubConfiguration.cs b/src/Testcontainers.PubSub/PubSubConfiguration.cs
new file mode 100644
index 000000000..f093f497b
--- /dev/null
+++ b/src/Testcontainers.PubSub/PubSubConfiguration.cs
@@ -0,0 +1,53 @@
+namespace Testcontainers.PubSub;
+
+///
+[PublicAPI]
+public sealed class PubSubConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PubSubConfiguration()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public PubSubConfiguration(IResourceConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public PubSubConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public PubSubConfiguration(PubSubConfiguration resourceConfiguration)
+ : this(new PubSubConfiguration(), resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The old Docker resource configuration.
+ /// The new Docker resource configuration.
+ public PubSubConfiguration(PubSubConfiguration oldValue, PubSubConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.PubSub/PubSubContainer.cs b/src/Testcontainers.PubSub/PubSubContainer.cs
new file mode 100644
index 000000000..32b07879f
--- /dev/null
+++ b/src/Testcontainers.PubSub/PubSubContainer.cs
@@ -0,0 +1,25 @@
+namespace Testcontainers.PubSub;
+
+///
+[PublicAPI]
+public sealed class PubSubContainer : DockerContainer
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ /// The logger.
+ public PubSubContainer(PubSubConfiguration configuration, ILogger logger)
+ : base(configuration, logger)
+ {
+ }
+
+ ///
+ /// Gets the PubSub emulator endpoint.
+ ///
+ /// The PubSub emulator endpoint.
+ public string GetEmulatorEndpoint()
+ {
+ return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(PubSubBuilder.PubSubPort)).ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.PubSub/Testcontainers.PubSub.csproj b/src/Testcontainers.PubSub/Testcontainers.PubSub.csproj
new file mode 100644
index 000000000..4c05d521f
--- /dev/null
+++ b/src/Testcontainers.PubSub/Testcontainers.PubSub.csproj
@@ -0,0 +1,13 @@
+
+
+ netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.PubSub/Usings.cs b/src/Testcontainers.PubSub/Usings.cs
new file mode 100644
index 000000000..bf2829a65
--- /dev/null
+++ b/src/Testcontainers.PubSub/Usings.cs
@@ -0,0 +1,7 @@
+global using System;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using JetBrains.Annotations;
+global using Microsoft.Extensions.Logging;
\ No newline at end of file
diff --git a/tests/Testcontainers.PubSub.Tests/.editorconfig b/tests/Testcontainers.PubSub.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.PubSub.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.PubSub.Tests/PubSubContainerTests.cs b/tests/Testcontainers.PubSub.Tests/PubSubContainerTests.cs
new file mode 100644
index 000000000..03d0ae641
--- /dev/null
+++ b/tests/Testcontainers.PubSub.Tests/PubSubContainerTests.cs
@@ -0,0 +1,70 @@
+namespace Testcontainers.PubSub;
+
+public sealed class PubSubContainerTests : IAsyncLifetime
+{
+ private readonly PubSubContainer _pubSubContainer = new PubSubBuilder().Build();
+
+ public Task InitializeAsync()
+ {
+ return _pubSubContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _pubSubContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task SubTopicReturnsPubMessage()
+ {
+ // Given
+ const string helloPubSub = "Hello, PubSub!";
+
+ const string projectId = "hello-pub-sub";
+
+ const string topicId = "hello-topic";
+
+ const string subscriptionId = "hello-subscription";
+
+ var topicName = new TopicName(projectId, topicId);
+
+ var subscriptionName = new SubscriptionName(projectId, subscriptionId);
+
+ var message = new PubsubMessage();
+ message.Data = ByteString.CopyFromUtf8(helloPubSub);
+
+ var publisherClientBuilder = new PublisherServiceApiClientBuilder();
+ publisherClientBuilder.Endpoint = _pubSubContainer.GetEmulatorEndpoint();
+ publisherClientBuilder.ChannelCredentials = ChannelCredentials.Insecure;
+
+ var subscriberClientBuilder = new SubscriberServiceApiClientBuilder();
+ subscriberClientBuilder.Endpoint = _pubSubContainer.GetEmulatorEndpoint();
+ subscriberClientBuilder.ChannelCredentials = ChannelCredentials.Insecure;
+
+ // When
+ var publisher = await publisherClientBuilder.BuildAsync()
+ .ConfigureAwait(false);
+
+ _ = await publisher.CreateTopicAsync(topicName)
+ .ConfigureAwait(false);
+
+ var subscriber = await subscriberClientBuilder.BuildAsync()
+ .ConfigureAwait(false);
+
+ _ = await subscriber.CreateSubscriptionAsync(subscriptionName, topicName, null, 60)
+ .ConfigureAwait(false);
+
+ _ = await publisher.PublishAsync(topicName, new[] { message })
+ .ConfigureAwait(false);
+
+ var response = await subscriber.PullAsync(subscriptionName, 1)
+ .ConfigureAwait(false);
+
+ await subscriber.AcknowledgeAsync(subscriptionName, response.ReceivedMessages.Select(receivedMessage => receivedMessage.AckId))
+ .ConfigureAwait(false);
+
+ // Then
+ Assert.Equal(helloPubSub, response.ReceivedMessages.Single().Message.Data.ToStringUtf8());
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.PubSub.Tests/Testcontainers.PubSub.Tests.csproj b/tests/Testcontainers.PubSub.Tests/Testcontainers.PubSub.Tests.csproj
new file mode 100644
index 000000000..66d05c5b2
--- /dev/null
+++ b/tests/Testcontainers.PubSub.Tests/Testcontainers.PubSub.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+ net6.0
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.PubSub.Tests/Usings.cs b/tests/Testcontainers.PubSub.Tests/Usings.cs
new file mode 100644
index 000000000..c6e804fe3
--- /dev/null
+++ b/tests/Testcontainers.PubSub.Tests/Usings.cs
@@ -0,0 +1,7 @@
+global using System.Linq;
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using Google.Cloud.PubSub.V1;
+global using Google.Protobuf;
+global using Grpc.Core;
+global using Xunit;
\ No newline at end of file