diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index 8d363cb978f..027a7adc170 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -41,6 +41,7 @@ body:
- MySQL
- Neo4j
- NGINX
+ - OpenFGA
- Oracle Free
- Oracle XE
- OrientDB
diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml
index 6ee160c982b..c2f6d7504e8 100644
--- a/.github/ISSUE_TEMPLATE/enhancement.yaml
+++ b/.github/ISSUE_TEMPLATE/enhancement.yaml
@@ -41,6 +41,7 @@ body:
- MySQL
- Neo4j
- NGINX
+ - OpenFGA
- Oracle Free
- Oracle XE
- OrientDB
diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml
index 3f8920b4059..3a03aa09bdd 100644
--- a/.github/ISSUE_TEMPLATE/feature.yaml
+++ b/.github/ISSUE_TEMPLATE/feature.yaml
@@ -41,6 +41,7 @@ body:
- MySQL
- Neo4j
- NGINX
+ - OpenFGA
- Oracle Free
- Oracle XE
- OrientDB
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index bf9b145f2b3..163bf2db040 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -211,6 +211,10 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
+ - package-ecosystem: "gradle"
+ directory: "/modules/openfga"
+ schedule:
+ interval: "weekly"
- package-ecosystem: "gradle"
directory: "/modules/oracle-free"
schedule:
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 0fd1ec90d29..c7eec3b4a6a 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -131,6 +131,10 @@
- changed-files:
- any-glob-to-any-file:
- modules/nginx/**/*
+"modules/openfga":
+ - changed-files:
+ - any-glob-to-any-file:
+ - modules/openfga/**/*
"modules/oracle":
- changed-files:
- any-glob-to-any-file:
diff --git a/.github/settings.yml b/.github/settings.yml
index a4589aab643..c7458f63fc3 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -190,6 +190,9 @@ labels:
- name: modules/nginx
color: '#006b75'
+ - name: modules/openfga
+ color: '#006b75'
+
- name: modules/oracle
color: '#006b75'
diff --git a/docs/modules/openfga.md b/docs/modules/openfga.md
new file mode 100644
index 00000000000..e11cae3560a
--- /dev/null
+++ b/docs/modules/openfga.md
@@ -0,0 +1,30 @@
+# OpenFGA
+
+Testcontainers module for [OpenFGA](https://hub.docker.com/r/"openfga/openfga).
+
+## OpenFGAContainer's usage examples
+
+You can start an OpenFGA container instance from any Java application by using:
+
+
+[OpenFGA container](../../modules/openfga/src/test/java/org/testcontainers/openfga/OpenFGAContainerTest.java) inside_block:container
+
+
+## Adding this module to your project dependencies
+
+Add the following dependency to your `pom.xml`/`build.gradle` file:
+
+=== "Gradle"
+ ```groovy
+ testImplementation "org.testcontainers:openfga:{{latest_version}}"
+ ```
+
+=== "Maven"
+ ```xml
+
+ org.testcontainers
+ openfga
+ {{latest_version}}
+ test
+
+ ```
diff --git a/mkdocs.yml b/mkdocs.yml
index 784159e8e57..d51576a0bf6 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -88,6 +88,7 @@ nav:
- modules/minio.md
- modules/mockserver.md
- modules/nginx.md
+ - modules/openfga.md
- modules/pulsar.md
- modules/qdrant.md
- modules/rabbitmq.md
diff --git a/modules/openfga/build.gradle b/modules/openfga/build.gradle
new file mode 100644
index 00000000000..9824603d5bd
--- /dev/null
+++ b/modules/openfga/build.gradle
@@ -0,0 +1,21 @@
+description = "Testcontainers :: OpenFGA"
+
+dependencies {
+ api project(':testcontainers')
+
+ testImplementation 'org.assertj:assertj-core:3.25.1'
+ testImplementation 'dev.openfga:openfga-sdk:0.3.2'
+}
+
+test {
+ javaLauncher = javaToolchains.launcherFor {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
+}
+
+compileTestJava {
+ javaCompiler = javaToolchains.compilerFor {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
+ options.release.set(11)
+}
diff --git a/modules/openfga/src/main/java/org/testcontainers/openfga/OpenFGAContainer.java b/modules/openfga/src/main/java/org/testcontainers/openfga/OpenFGAContainer.java
new file mode 100644
index 00000000000..986e385c0d3
--- /dev/null
+++ b/modules/openfga/src/main/java/org/testcontainers/openfga/OpenFGAContainer.java
@@ -0,0 +1,45 @@
+package org.testcontainers.openfga;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * Testcontainers implementation for OpenFGA.
+ *
+ * Supported image: {@code openfga/openfga}
+ *
+ * Exposed ports:
+ *
+ * - Playground: 3000
+ * - HTTP: 8080
+ * - gRPC: 8081
+ *
+ */
+public class OpenFGAContainer extends GenericContainer {
+
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("openfga/openfga");
+
+ public OpenFGAContainer(String image) {
+ this(DockerImageName.parse(image));
+ }
+
+ public OpenFGAContainer(DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+
+ withExposedPorts(3000, 8080, 8081);
+ withCommand("run");
+ waitingFor(
+ Wait.forHttp("/healthz").forPort(8080).forResponsePredicate(response -> response.contains("SERVING"))
+ );
+ }
+
+ public String getHttpEndpoint() {
+ return "http://" + getHost() + ":" + getMappedPort(8080);
+ }
+
+ public String getGrpcEndpoint() {
+ return "http://" + getHost() + ":" + getMappedPort(8081);
+ }
+}
diff --git a/modules/openfga/src/test/java/org/testcontainers/openfga/OpenFGAContainerTest.java b/modules/openfga/src/test/java/org/testcontainers/openfga/OpenFGAContainerTest.java
new file mode 100644
index 00000000000..688d4d31f10
--- /dev/null
+++ b/modules/openfga/src/test/java/org/testcontainers/openfga/OpenFGAContainerTest.java
@@ -0,0 +1,33 @@
+package org.testcontainers.openfga;
+
+import dev.openfga.sdk.api.client.OpenFgaClient;
+import dev.openfga.sdk.api.client.model.ClientCreateStoreResponse;
+import dev.openfga.sdk.api.configuration.ClientConfiguration;
+import dev.openfga.sdk.api.model.CreateStoreRequest;
+import dev.openfga.sdk.errors.FgaInvalidParameterException;
+import org.junit.Test;
+
+import java.util.concurrent.ExecutionException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenFGAContainerTest {
+
+ @Test
+ public void withDefaultConfig() throws FgaInvalidParameterException, ExecutionException, InterruptedException {
+ try ( // container
+ OpenFGAContainer openfga = new OpenFGAContainer("openfga/openfga:v1.4.3")
+ // }
+ ) {
+ openfga.start();
+
+ ClientConfiguration config = new ClientConfiguration().apiUrl(openfga.getHttpEndpoint());
+ OpenFgaClient client = new OpenFgaClient(config);
+
+ assertThat(client.listStores().get().getStores()).isEmpty();
+ ClientCreateStoreResponse store = client.createStore(new CreateStoreRequest().name("test")).get();
+ assertThat(store.getId()).isNotNull();
+ assertThat(client.listStores().get().getStores()).hasSize(1);
+ }
+ }
+}
diff --git a/modules/openfga/src/test/resources/logback-test.xml b/modules/openfga/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..83ef7a1a3ef
--- /dev/null
+++ b/modules/openfga/src/test/resources/logback-test.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level %logger - %msg%n
+
+
+
+
+
+
+
+
+