diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index e12f947b2..99186917f 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -2,11 +2,11 @@ name: Continuous Integration & Delivery
on:
push:
- branches: [ develop, main, bugfix/*, feature/* ]
- paths-ignore: [ 'docs/**', 'examples/**' ]
+ branches: [develop, main, bugfix/*, feature/*]
+ paths-ignore: ["docs/**", "examples/**"]
pull_request:
- branches: [ develop, main ]
- paths-ignore: [ 'docs/**', 'examples/**' ]
+ branches: [develop, main]
+ paths-ignore: ["docs/**", "examples/**"]
workflow_dispatch:
inputs:
publish_nuget_package:
@@ -31,72 +31,77 @@ jobs:
strategy:
max-parallel: 6
matrix:
- test-projects: [
- { name: "Testcontainers", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Platform.Linux", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Platform.Windows", runs-on: "windows-2022" },
- { name: "Testcontainers.Databases", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.ResourceReaper", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.ActiveMq", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.ArangoDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Azurite", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.BigQuery", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Bigtable", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Cassandra", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.ClickHouse", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.CockroachDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Consul", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.CosmosDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Couchbase", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.CouchDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Db2", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.DynamoDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Elasticsearch", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.EventHubs", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.EventStoreDb", 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" },
- { name: "Testcontainers.InfluxDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.JanusGraph", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.K3s", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Kafka", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Keycloak", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Kusto", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.LocalStack", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.LowkeyVault", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.MariaDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Milvus", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Minio", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.MongoDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.MsSql", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.MySql", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Nats", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Neo4j", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Ollama", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.OpenSearch", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Oracle", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Oracle11", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Oracle18", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Oracle21", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Oracle23", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Papercut", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.PostgreSql", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.PubSub", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Pulsar", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Qdrant", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.RabbitMq", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.RavenDb", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Redis", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Redpanda", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.ServiceBus", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Sftp", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Typesense", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Weaviate", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.WebDriver", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.Xunit", runs-on: "ubuntu-22.04" },
- { name: "Testcontainers.XunitV3", runs-on: "ubuntu-22.04" }
- ]
+ test-projects:
+ [
+ { name: "Testcontainers", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Platform.Linux", runs-on: "ubuntu-22.04" },
+ {
+ name: "Testcontainers.Platform.Windows",
+ runs-on: "windows-2022",
+ },
+ { name: "Testcontainers.Databases", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.ResourceReaper", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.ActiveMq", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.ArangoDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Azurite", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.BigQuery", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Bigtable", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Cassandra", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.ClickHouse", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.CockroachDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Consul", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.CosmosDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Couchbase", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.CouchDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Db2", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.DynamoDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Elasticsearch", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.EventHubs", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.EventStoreDb", 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" },
+ { name: "Testcontainers.InfluxDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.JanusGraph", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.K3s", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Kafka", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Keycloak", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Kusto", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.LocalStack", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.LowkeyVault", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.MariaDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Milvus", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Minio", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.MongoDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.MsSql", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.MySql", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Nats", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Neo4j", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Ollama", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.OpenSearch", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Oracle", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Oracle11", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Oracle18", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Oracle21", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Oracle23", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Papercut", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.PostgreSql", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.PubSub", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Pulsar", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Qdrant", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.RabbitMq", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.RavenDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Redis", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Redpanda", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.ServiceBus", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Sftp", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Typesense", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Weaviate", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.WebDriver", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.Xunit", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.XunitV3", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.SpiceDB", runs-on: "ubuntu-22.04" },
+ ]
runs-on: ${{ matrix.test-projects.runs-on }}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3ab6849e8..0f6482bfc 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -7,6 +7,7 @@
+
@@ -61,6 +62,8 @@
+
+
diff --git a/Testcontainers.sln b/Testcontainers.sln
index e09e2b093..e82e901d3 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -257,6 +257,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Xunit.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.XunitV3.Tests", "tests\Testcontainers.XunitV3.Tests\Testcontainers.XunitV3.Tests.csproj", "{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.SpiceDB", "src\Testcontainers.SpiceDB\Testcontainers.SpiceDB.csproj", "{64B27088-14DC-4CA2-B24E-5D0D5BA14355}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.SpiceDB.Tests", "tests\Testcontainers.SpiceDB.Tests\Testcontainers.SpiceDB.Tests.csproj", "{21D155EB-A843-4D4D-84E1-5C913217BE89}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -751,6 +755,18 @@ Global
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {64B27088-14DC-4CA2-B24E-5D0D5BA14355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {64B27088-14DC-4CA2-B24E-5D0D5BA14355}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {64B27088-14DC-4CA2-B24E-5D0D5BA14355}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {64B27088-14DC-4CA2-B24E-5D0D5BA14355}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A9F554E5-9183-4F8A-B226-58DD46BB2060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A9F554E5-9183-4F8A-B226-58DD46BB2060}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A9F554E5-9183-4F8A-B226-58DD46BB2060}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A9F554E5-9183-4F8A-B226-58DD46BB2060}.Release|Any CPU.Build.0 = Release|Any CPU
+ {21D155EB-A843-4D4D-84E1-5C913217BE89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {21D155EB-A843-4D4D-84E1-5C913217BE89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {21D155EB-A843-4D4D-84E1-5C913217BE89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {21D155EB-A843-4D4D-84E1-5C913217BE89}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -878,5 +894,7 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E901DF14-6F05-4FC2-825A-3055FAD33561} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {64B27088-14DC-4CA2-B24E-5D0D5BA14355} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {21D155EB-A843-4D4D-84E1-5C913217BE89} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
diff --git a/docs/modules/index.md b/docs/modules/index.md
index 254a2d9fe..476749cd2 100644
--- a/docs/modules/index.md
+++ b/docs/modules/index.md
@@ -20,7 +20,7 @@ await moduleNameContainer.StartAsync();
We will add module-specific documentations soon.
| Module | Image | NuGet | Source |
-|-------------------|---------------------------------------------------------------------|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
+| ----------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| 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) |
@@ -73,6 +73,7 @@ await moduleNameContainer.StartAsync();
| Typesense | `typesense/typesense:28.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Typesense) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Typesense) |
| Weaviate | `semitechnologies/weaviate:1.26.14` | [NuGet](https://www.nuget.org/packages/Testcontainers.Weaviate) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Weaviate) |
| WebDriver | `selenium/standalone-chrome:110.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.WebDriver) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.WebDriver) |
+| SpiceDB | `authzed/spicedb:v1.45.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.SpiceDB) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.SpiceDB) |
## Implement a module
diff --git a/docs/modules/spicedb.md b/docs/modules/spicedb.md
new file mode 100644
index 000000000..c3ea8951d
--- /dev/null
+++ b/docs/modules/spicedb.md
@@ -0,0 +1,56 @@
+# SpiceDB
+
+[SpiceDB](https://authzed.com/spicedb/) is an open-source, Google Zanzibar-inspired permissions database that provides a centralized service to store, compute, and validate application permissions. It enables fine-grained authorization at scale with a flexible relationship-based permission model.
+
+Add the following dependency to your project file:
+
+```shell title="NuGet"
+dotnet add package Testcontainers.SpiceDB
+```
+
+You can start a SpiceDB container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.
+
+=== "Usage Example"
+
+````csharp
+public sealed class SpiceDBContainerTest : IAsyncLifetime
+{
+private readonly SpiceDBContainer \_spicedbContainer = new SpiceDBBuilder().Build();
+
+ public Task InitializeAsync()
+ {
+ return _spicedbContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _spicedbContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ public async Task SpiceDBContainer_IsRunning_ReturnsTrue()
+ {
+ // Given
+ var containerState = await _spicedbContainer.GetStateAsync();
+
+ // ...
+ }
+ }
+ ```
+
+The test example uses the following NuGet dependencies:
+
+=== "Package References"
+`xml
+
+
+
+
+
+
+ `
+
+To execute the tests, use the command `dotnet test` from a terminal.
+
+--8<-- "docs/modules/\_call_out_test_projects.txt"
+````
diff --git a/src/Testcontainers.SpiceDB/.editorconfig b/src/Testcontainers.SpiceDB/.editorconfig
new file mode 100644
index 000000000..78b36ca08
--- /dev/null
+++ b/src/Testcontainers.SpiceDB/.editorconfig
@@ -0,0 +1 @@
+root = true
diff --git a/src/Testcontainers.SpiceDB/SpiceDBBuilder.cs b/src/Testcontainers.SpiceDB/SpiceDBBuilder.cs
new file mode 100644
index 000000000..21bae0447
--- /dev/null
+++ b/src/Testcontainers.SpiceDB/SpiceDBBuilder.cs
@@ -0,0 +1,71 @@
+namespace Testcontainers.SpiceDB;
+
+///
+[PublicAPI]
+public sealed class SpiceDBBuilder : ContainerBuilder
+{
+ public const string SpiceDBImage = "authzed/spicedb:v1.45.1";
+
+ public const ushort SpiceDBgRPCPort = 50051;
+
+ public const ushort SpiceDBgHTTPPort = 8443;
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SpiceDBBuilder()
+ : this(new SpiceDBConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private SpiceDBBuilder(SpiceDBConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override SpiceDBConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ public override SpiceDBContainer Build()
+ {
+ Validate();
+ return new SpiceDBContainer(DockerResourceConfiguration);
+ }
+
+ ///
+ protected override SpiceDBBuilder Init()
+ {
+ return base.Init()
+ .WithImage(SpiceDBImage)
+ .WithPortBinding(SpiceDBgRPCPort, true)
+ .WithPortBinding(SpiceDBgHTTPPort, true)
+ .WithCommand("serve", $"--grpc-preshared-key={DockerResourceConfiguration.GrpcPresharedKey}", $"--datastore-engine={DockerResourceConfiguration.DatastoreEngine}", $"--log-level=info")
+ .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("grpc server started serving"));
+ }
+
+ ///
+ protected override SpiceDBBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new SpiceDBConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override SpiceDBBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new SpiceDBConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override SpiceDBBuilder Merge(SpiceDBConfiguration oldValue, SpiceDBConfiguration newValue)
+ {
+ return new SpiceDBBuilder(new SpiceDBConfiguration(oldValue, newValue));
+ }
+}
diff --git a/src/Testcontainers.SpiceDB/SpiceDBConfiguration.cs b/src/Testcontainers.SpiceDB/SpiceDBConfiguration.cs
new file mode 100644
index 000000000..d113b20d4
--- /dev/null
+++ b/src/Testcontainers.SpiceDB/SpiceDBConfiguration.cs
@@ -0,0 +1,63 @@
+namespace Testcontainers.SpiceDB;
+
+///
+[PublicAPI]
+public sealed class SpiceDBConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SpiceDBConfiguration(string grpcPresharedKey = "mysecret", string datastoreEngine = "memory", bool? tslEnabled = false)
+ {
+ GrpcPresharedKey = grpcPresharedKey;
+ DatastoreEngine = datastoreEngine;
+ TslEnabled = tslEnabled.GetValueOrDefault(false);
+ }
+
+ public bool TslEnabled { get; set; }
+
+ public string GrpcPresharedKey { get; set; }
+
+ public string DatastoreEngine { get; set; }
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public SpiceDBConfiguration(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 SpiceDBConfiguration(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 SpiceDBConfiguration(SpiceDBConfiguration resourceConfiguration)
+ : this(new SpiceDBConfiguration(), 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 SpiceDBConfiguration(SpiceDBConfiguration oldValue, SpiceDBConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ }
+}
diff --git a/src/Testcontainers.SpiceDB/SpiceDBContainer.cs b/src/Testcontainers.SpiceDB/SpiceDBContainer.cs
new file mode 100644
index 000000000..73e0ab107
--- /dev/null
+++ b/src/Testcontainers.SpiceDB/SpiceDBContainer.cs
@@ -0,0 +1,28 @@
+using System.Threading;
+using Health = Grpc.Health.V1.Health;
+
+namespace Testcontainers.SpiceDB;
+
+///
+[PublicAPI]
+public sealed class SpiceDBContainer : DockerContainer
+{
+ SpiceDBConfiguration _configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ public SpiceDBContainer(SpiceDBConfiguration configuration)
+ : base(configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public string GetGrpcConnectionString()
+ {
+ var scheme = _configuration.TslEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
+ var endpoint = new UriBuilder(scheme, Hostname, GetMappedPublicPort(SpiceDBBuilder.SpiceDBgRPCPort));
+ return endpoint.ToString();
+ }
+}
diff --git a/src/Testcontainers.SpiceDB/Testcontainers.SpiceDB.csproj b/src/Testcontainers.SpiceDB/Testcontainers.SpiceDB.csproj
new file mode 100644
index 000000000..cdaaeff67
--- /dev/null
+++ b/src/Testcontainers.SpiceDB/Testcontainers.SpiceDB.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0;net9.0;netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Testcontainers.SpiceDB/Usings.cs b/src/Testcontainers.SpiceDB/Usings.cs
new file mode 100644
index 000000000..9b95586a0
--- /dev/null
+++ b/src/Testcontainers.SpiceDB/Usings.cs
@@ -0,0 +1,9 @@
+global using System;
+global using System.Threading.Tasks;
+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 Grpc.Health.V1;
+global using Grpc.Core;
\ No newline at end of file
diff --git a/tests/Testcontainers.SpiceDB.Tests/.editorconfig b/tests/Testcontainers.SpiceDB.Tests/.editorconfig
new file mode 100644
index 000000000..14403c511
--- /dev/null
+++ b/tests/Testcontainers.SpiceDB.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
diff --git a/tests/Testcontainers.SpiceDB.Tests/SpiceDBContainerTest.cs b/tests/Testcontainers.SpiceDB.Tests/SpiceDBContainerTest.cs
new file mode 100644
index 000000000..97c825e2e
--- /dev/null
+++ b/tests/Testcontainers.SpiceDB.Tests/SpiceDBContainerTest.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Net.Client;
+using Xunit;
+
+namespace Testcontainers.SpiceDB;
+
+public sealed class SpiceDBContainerTest : IAsyncLifetime
+{
+ private readonly SpiceDBContainer _spicedbContainer = new SpiceDBBuilder().Build();
+
+ public async ValueTask InitializeAsync()
+ {
+ await _spicedbContainer.StartAsync()
+ .ConfigureAwait(false);
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return _spicedbContainer.DisposeAsync();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public void ExpectedPortIsMapped()
+ {
+ // Given & When
+ var mappedPort = _spicedbContainer.GetMappedPublicPort(SpiceDBBuilder.SpiceDBgRPCPort);
+
+ // Then
+ Assert.True(mappedPort > 0);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task VersionCommandReturnsSuccessful()
+ {
+ // Given
+ List commands = ["spicedb", "version"];
+
+ // When
+ var execResult = await _spicedbContainer.ExecAsync(commands, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public void GetGrpcConnectionStringNotNull()
+ {
+ // Given & When
+ var connectionString = _spicedbContainer.GetGrpcConnectionString();
+
+ // Then
+ Assert.NotNull(connectionString);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task ShouldConnectToSpiceDB()
+ {
+ // Given
+ var connectionString = _spicedbContainer.GetGrpcConnectionString();
+ var handler = new SocketsHttpHandler();
+ handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
+
+ // When
+ using var channel = GrpcChannel.ForAddress(connectionString, new GrpcChannelOptions
+ {
+ HttpHandler = handler
+ });
+
+ // Then
+ Assert.NotNull(connectionString);
+ Assert.NotNull(channel);
+
+ // Test connectivity by attempting to connect
+ await channel.ConnectAsync();
+ Assert.Equal(ConnectivityState.Ready, channel.State);
+ }
+}
diff --git a/tests/Testcontainers.SpiceDB.Tests/Testcontainers.SpiceDB.Tests.csproj b/tests/Testcontainers.SpiceDB.Tests/Testcontainers.SpiceDB.Tests.csproj
new file mode 100644
index 000000000..d1ba0366b
--- /dev/null
+++ b/tests/Testcontainers.SpiceDB.Tests/Testcontainers.SpiceDB.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+ net9.0
+ false
+ false
+ Exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Testcontainers.SpiceDB.Tests/Usings.cs b/tests/Testcontainers.SpiceDB.Tests/Usings.cs
new file mode 100644
index 000000000..21f33a999
--- /dev/null
+++ b/tests/Testcontainers.SpiceDB.Tests/Usings.cs
@@ -0,0 +1,6 @@
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using Testcontainers.SpiceDB;
+global using Xunit;
+global using Grpc.Core;
+global using Grpc.Net.Client;