diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 3ca93f1945f..583233766a1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -17,6 +17,7 @@ body: - ActiveMQ - Azure - Cassandra + - ChromaDB - Clickhouse - CockroachDB - Consul @@ -56,6 +57,7 @@ body: - ToxiProxy - Trino - Vault + - Weaviate - YugabyteDB validations: required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 914e59d7649..77a13321716 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -17,6 +17,7 @@ body: - ActiveMQ - Azure - Cassandra + - ChromaDB - Clickhouse - CockroachDB - Consul @@ -56,6 +57,7 @@ body: - ToxiProxy - Trino - Vault + - Weaviate - YugabyteDB validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 6a40cd78168..e503bf4ff86 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -17,6 +17,7 @@ body: - ActiveMQ - Azure - Cassandra + - ChromaDB - Clickhouse - CockroachDB - CrateDB @@ -56,6 +57,7 @@ body: - ToxiProxy - Trino - Vault + - Weaviate - YugabyteDB - New Module - type: textarea diff --git a/.github/actions/setup-gradle/action.yml b/.github/actions/setup-gradle/action.yml index 58499f75e57..3cb582c0cf8 100644 --- a/.github/actions/setup-gradle/action.yml +++ b/.github/actions/setup-gradle/action.yml @@ -4,7 +4,7 @@ runs: using: "composite" steps: - name: Setup Gradle Build Action - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: gradle-home-cache-includes: | caches diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0b9eea13c94..e0f1bdfda25 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -51,6 +51,11 @@ updates: ignore: - dependency-name: "io.dropwizard.metrics:metrics-core" update-types: [ "version-update:semver-major" ] + - package-ecosystem: "gradle" + directory: "/modules/chromadb" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/clickhouse" schedule: @@ -310,6 +315,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/weaviate" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/yugabytedb" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index 998a0e8364a..9d0eb0a6e07 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -27,6 +27,10 @@ - changed-files: - any-glob-to-any-file: - modules/cassandra/**/* +"modules/chromadb": + - changed-files: + - any-glob-to-any-file: + - modules/chromadb/**/* "modules/clickhouse": - changed-files: - any-glob-to-any-file: @@ -196,6 +200,10 @@ - changed-files: - any-glob-to-any-file: - modules/vault/**/* +"modules/weaviate": + - changed-files: + - any-glob-to-any-file: + - modules/weaviate/**/* "modules/yugabytedb": - changed-files: - any-glob-to-any-file: diff --git a/.github/settings.yml b/.github/settings.yml index 3047612d756..12629f2f355 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -115,6 +115,9 @@ labels: - name: modules/cassandra color: '#006b75' + - name: modules/chromadb + color: '#006b75' + - name: modules/clickhouse color: '#006b75' @@ -238,6 +241,9 @@ labels: - name: modules/vault color: '#006b75' + - name: modules/weaviate + color: '#006b75' + - name: modules/yugabytedb color: '#006b75' diff --git a/.github/workflows/ci-rootless.yml b/.github/workflows/ci-rootless.yml index fccaef5ede7..21ff4f793c6 100644 --- a/.github/workflows/ci-rootless.yml +++ b/.github/workflows/ci-rootless.yml @@ -52,7 +52,7 @@ jobs: - name: Remove Docket root socket run: sudo rm -rf /var/run/docker.sock - name: Setup Gradle Build Action - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - name: Build with Gradle run: ./gradlew --no-daemon --scan testcontainers:test --tests '*GenericContainerRuleTest' - uses: ./.github/actions/setup-junit-report diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15b1240904b..8e07cd76218 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-java - name: Setup Gradle Build Action - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - id: set-matrix env: # Since we override the tests executor, @@ -122,7 +122,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-java - name: Setup Gradle Build Action - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - id: set-matrix working-directory: ./examples/ env: @@ -159,7 +159,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-java - name: Setup Gradle Build Action - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - id: set-matrix env: # Since we override the tests executor, diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fbd63cdbd1..79a0d6856b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: run: docker image prune -af - name: Setup Gradle Build Action - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - name: Run Gradle Build run: ./gradlew build --scan --no-daemon -i -x test diff --git a/core/build.gradle b/core/build.gradle index 102d3997b8a..a318ff00b10 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -88,8 +88,8 @@ dependencies { shaded 'org.awaitility:awaitility:4.2.0' - api platform('com.github.docker-java:docker-java-bom:3.3.4') - shaded platform('com.github.docker-java:docker-java-bom:3.3.4') + api platform('com.github.docker-java:docker-java-bom:3.3.5') + shaded platform('com.github.docker-java:docker-java-bom:3.3.5') api "com.github.docker-java:docker-java-api" diff --git a/docs/modules/chromadb.md b/docs/modules/chromadb.md new file mode 100644 index 00000000000..9b14dfc2970 --- /dev/null +++ b/docs/modules/chromadb.md @@ -0,0 +1,30 @@ +# ChromaDB + +Testcontainers module for [ChromaDB](https://registry.hub.docker.com/r/chromadb/chroma) + +## ChromaDB's usage examples + +You can start a ChromaDB container instance from any Java application by using: + + +[Default ChromaDB container](../../modules/chromadb/src/test/java/org/testcontainers/chromadb/ChromaDBContainerTest.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:chromadb:{{latest_version}}" +``` + +=== "Maven" +```xml + +org.testcontainers +chromadb +{{latest_version}} +test + +``` diff --git a/docs/modules/databases/postgres.md b/docs/modules/databases/postgres.md index b7632d78293..d07e77937b9 100644 --- a/docs/modules/databases/postgres.md +++ b/docs/modules/databases/postgres.md @@ -2,6 +2,28 @@ See [Database containers](./index.md) for documentation and usage that is common to all relational database container types. +## Compatible images + +`PostgreSQLContainer` can also be used with the following images: + +* [pgvector/pgvector](https://hub.docker.com/r/pgvector/pgvector) + + +[Using pgvector](../../../modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java) inside_block:pgvectorContainer + + +* [postgis/postgis](https://registry.hub.docker.com/r/postgis/postgis) + + +[Using PostGIS](../../../modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java) inside_block:postgisContainer + + +* [timescale/timescaledb](https://hub.docker.com/r/timescale/timescaledb) + + +[Using TimescaleDB](../../../modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java) inside_block:timescaledbContainer + + ## Adding this module to your project dependencies Add the following dependency to your `pom.xml`/`build.gradle` file: diff --git a/docs/modules/weaviate.md b/docs/modules/weaviate.md new file mode 100644 index 00000000000..da028cf3ce1 --- /dev/null +++ b/docs/modules/weaviate.md @@ -0,0 +1,30 @@ +# Weaviate + +Testcontainers module for [Weaviate](https://hub.docker.com/r/semitechnologies/weaviate) + +## WeaviateContainer's usage examples + +You can start a Weaviate container instance from any Java application by using: + + +[Default Weaviate container](../../modules/weaviate/src/test/java/org/testcontainers/weaviate/WeaviateContainerTest.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:weaviate:{{latest_version}}" +``` + +=== "Maven" +```xml + +org.testcontainers +weaviate +{{latest_version}} +test + +``` diff --git a/mkdocs.yml b/mkdocs.yml index 31afa5ea9e5..d912f7a0366 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,6 +75,7 @@ nav: - modules/databases/yugabytedb.md - modules/activemq.md - modules/azure.md + - modules/chromadb.md - modules/consul.md - modules/docker_compose.md - modules/elasticsearch.md @@ -94,6 +95,7 @@ nav: - modules/solr.md - modules/toxiproxy.md - modules/vault.md + - modules/weaviate.md - modules/webdriver_containers.md - Test framework integration: - test_framework_integration/junit_4.md diff --git a/modules/chromadb/build.gradle b/modules/chromadb/build.gradle new file mode 100644 index 00000000000..2b170c21570 --- /dev/null +++ b/modules/chromadb/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: ChromaDB" + +dependencies { + api project(':testcontainers') + + testImplementation 'org.assertj:assertj-core:3.25.1' + testImplementation 'io.rest-assured:rest-assured:5.4.0' +} diff --git a/modules/chromadb/src/main/java/org/testcontainers/chromadb/ChromaDBContainer.java b/modules/chromadb/src/main/java/org/testcontainers/chromadb/ChromaDBContainer.java new file mode 100644 index 00000000000..a1bccf3904f --- /dev/null +++ b/modules/chromadb/src/main/java/org/testcontainers/chromadb/ChromaDBContainer.java @@ -0,0 +1,34 @@ +package org.testcontainers.chromadb; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +/** + * Testcontainers implementation of ChromaDB. + *

+ * Supported images: {@code chromadb/chroma}, {@code ghcr.io/chroma-core/chroma} + *

+ * Exposed ports: 8000 + */ +public class ChromaDBContainer extends GenericContainer { + + private static final DockerImageName DEFAULT_DOCKER_IMAGE = DockerImageName.parse("chromadb/chroma"); + + private static final DockerImageName GHCR_DOCKER_IMAGE = DockerImageName.parse("ghcr.io/chroma-core/chroma"); + + public ChromaDBContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public ChromaDBContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_DOCKER_IMAGE, GHCR_DOCKER_IMAGE); + withExposedPorts(8000); + waitingFor(Wait.forHttp("/api/v1/heartbeat")); + } + + public String getEndpoint() { + return "http://" + getHost() + ":" + getFirstMappedPort(); + } +} diff --git a/modules/chromadb/src/test/java/org/testcontainers/chromadb/ChromaDBContainerTest.java b/modules/chromadb/src/test/java/org/testcontainers/chromadb/ChromaDBContainerTest.java new file mode 100644 index 00000000000..945af0dcbff --- /dev/null +++ b/modules/chromadb/src/test/java/org/testcontainers/chromadb/ChromaDBContainerTest.java @@ -0,0 +1,30 @@ +package org.testcontainers.chromadb; + +import io.restassured.http.ContentType; +import org.junit.Test; + +import static io.restassured.RestAssured.given; + +public class ChromaDBContainerTest { + + @Test + public void test() { + try ( // container { + ChromaDBContainer chroma = new ChromaDBContainer("chromadb/chroma:0.4.22") + // } + ) { + chroma.start(); + + given() + .baseUri(chroma.getEndpoint()) + .when() + .body("{\"name\": \"test\"}") + .contentType(ContentType.JSON) + .post("/api/v1/databases") + .then() + .statusCode(200); + + given().baseUri(chroma.getEndpoint()).when().get("/api/v1/databases/test").then().statusCode(200); + } + } +} diff --git a/modules/chromadb/src/test/resources/logback-test.xml b/modules/chromadb/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/chromadb/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + diff --git a/modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java b/modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java new file mode 100644 index 00000000000..2bb1c6c3d54 --- /dev/null +++ b/modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java @@ -0,0 +1,64 @@ +package org.testcontainers.containers; + +import org.junit.Test; +import org.testcontainers.db.AbstractContainerDatabaseTest; +import org.testcontainers.utility.DockerImageName; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompatibleImageTest extends AbstractContainerDatabaseTest { + + @Test + public void pgvector() throws SQLException { + try ( + // pgvectorContainer { + PostgreSQLContainer pgvector = new PostgreSQLContainer<>( + DockerImageName.parse("pgvector/pgvector:pg16").asCompatibleSubstituteFor("postgres") + ) + // } + ) { + pgvector.start(); + + ResultSet resultSet = performQuery(pgvector, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + } + } + + @Test + public void postgis() throws SQLException { + try ( + // postgisContainer { + PostgreSQLContainer postgis = new PostgreSQLContainer<>( + DockerImageName.parse("postgis/postgis:16-3.4-alpine").asCompatibleSubstituteFor("postgres") + ) + // } + ) { + postgis.start(); + + ResultSet resultSet = performQuery(postgis, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + } + } + + @Test + public void timescaledb() throws SQLException { + try ( + // timescaledbContainer { + PostgreSQLContainer timescaledb = new PostgreSQLContainer<>( + DockerImageName.parse("timescale/timescaledb:2.14.2-pg16").asCompatibleSubstituteFor("postgres") + ) + // } + ) { + timescaledb.start(); + + ResultSet resultSet = performQuery(timescaledb, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + } + } +} diff --git a/modules/weaviate/build.gradle b/modules/weaviate/build.gradle new file mode 100644 index 00000000000..06a77aa434a --- /dev/null +++ b/modules/weaviate/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: Weaviate" + +dependencies { + api project(':testcontainers') + + testImplementation 'org.assertj:assertj-core:3.25.1' + testImplementation 'io.weaviate:client:4.5.1' +} diff --git a/modules/weaviate/src/main/java/org/testcontainers/weaviate/WeaviateContainer.java b/modules/weaviate/src/main/java/org/testcontainers/weaviate/WeaviateContainer.java new file mode 100644 index 00000000000..bd217b13e75 --- /dev/null +++ b/modules/weaviate/src/main/java/org/testcontainers/weaviate/WeaviateContainer.java @@ -0,0 +1,36 @@ +package org.testcontainers.weaviate; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Testcontainers implementation of Weaviate. + *

+ * Supported image: {@code semitechnologies/weaviate} + *

+ * Exposed ports: + *

+ */ +public class WeaviateContainer extends GenericContainer { + + private static final String WEAVIATE_IMAGE = "semitechnologies/weaviate"; + + public WeaviateContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public WeaviateContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DockerImageName.parse(WEAVIATE_IMAGE)); + withExposedPorts(8080, 50051); + withEnv("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "true"); + withEnv("PERSISTENCE_DATA_PATH", "/var/lib/weaviate"); + } + + public String getHttpHostAddress() { + return getHost() + ":" + getMappedPort(8080); + } +} diff --git a/modules/weaviate/src/test/java/org/testcontainers/weaviate/WeaviateContainerTest.java b/modules/weaviate/src/test/java/org/testcontainers/weaviate/WeaviateContainerTest.java new file mode 100644 index 00000000000..562fc36de51 --- /dev/null +++ b/modules/weaviate/src/test/java/org/testcontainers/weaviate/WeaviateContainerTest.java @@ -0,0 +1,25 @@ +package org.testcontainers.weaviate; + +import io.weaviate.client.Config; +import io.weaviate.client.WeaviateClient; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.misc.model.Meta; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WeaviateContainerTest { + + @Test + public void test() { + try ( // container { + WeaviateContainer weaviate = new WeaviateContainer("semitechnologies/weaviate:1.22.4") + // } + ) { + weaviate.start(); + WeaviateClient client = new WeaviateClient(new Config("http", weaviate.getHttpHostAddress())); + Result meta = client.misc().metaGetter().run(); + assertThat(meta.getResult().getVersion()).isEqualTo("1.22.4"); + } + } +} diff --git a/modules/weaviate/src/test/resources/logback-test.xml b/modules/weaviate/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/weaviate/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + +