diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc index 2ed52679277..8085e26b757 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc @@ -35,6 +35,9 @@ The following service connection factories are provided in the `spring-ai-spring | Containers named `localstack/localstack` | `ChromaConnectionDetails` +| Containers named `docker.elastic.co/elasticsearch/elasticsearch` + +| `ElasticsearchConnectionDetails` | Containers named `chromadb/chroma`, `ghcr.io/chroma-core/chroma` | `MongoConnectionDetails` diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc index 2f8b59d4182..1e7813cedeb 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc @@ -38,6 +38,9 @@ The following service connection factories are provided in the `spring-ai-spring | `ChromaConnectionDetails` | Containers of type `ChromaDBContainer` +| `ElasticsearchConnectionDetails` +| Containers of type `ElasticsearchContainer` + | `MilvusServiceClientConnectionDetails` | Containers of type `MilvusContainer` diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml index 7f77cb1cbcd..864f2f63fac 100644 --- a/spring-ai-spring-boot-docker-compose/pom.xml +++ b/spring-ai-spring-boot-docker-compose/pom.xml @@ -55,6 +55,11 @@ ${project.parent.version} true + + org.springframework.ai + spring-ai-autoconfigure-vector-store-elasticsearch + ${project.parent.version} + org.springframework.ai spring-ai-autoconfigure-vector-store-weaviate @@ -138,6 +143,14 @@ true + + + org.springframework.ai + spring-ai-elasticsearch-store + ${project.parent.version} + true + + org.springframework.ai diff --git a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java new file mode 100644 index 00000000000..23aa0b1e95d --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.docker.compose.service.connection.elasticsearch; + +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +import java.util.List; + +/** + * A {@link DockerComposeConnectionDetailsFactory} implementation that creates + * {@link ElasticsearchConnectionDetails} for an Elasticsearch instance running in a + * Docker container. + * + * @author Laura Trotta + * @see DockerComposeConnectionDetailsFactory + * @see ElasticsearchConnectionDetails + * @see DockerComposeConnectionSource + * @since 1.0.2 + */ +class ElasticsearchDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int ELASTICSEARCH_PORT = 9200; + + protected ElasticsearchDockerComposeConnectionDetailsFactory() { + super("docker.elastic.co/elasticsearch/elasticsearch"); + } + + @Override + protected ElasticsearchConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ElasticsearchDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link ElasticsearchConnectionDetails} backed by an {@code Elasticsearch} + * {@link RunningService}. + */ + static class ElasticsearchDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements ElasticsearchConnectionDetails { + + private final ElasticsearchEnvironment environment; + + private final String host; + + ElasticsearchDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ElasticsearchEnvironment(service.env()); + this.host = service.host(); + } + + @Override + public List getNodes() { + return List.of(new Node(host, ELASTICSEARCH_PORT, Node.Protocol.HTTPS, getUsername(), getPassword())); + } + + @Override + public String getUsername() { + return "elastic"; + } + + @Override + public String getPassword() { + return environment.getPassword(); + } + + @Override + public String getPathPrefix() { + return ""; + } + + } + +} diff --git a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchEnvironment.java b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchEnvironment.java new file mode 100644 index 00000000000..35a449a8acf --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchEnvironment.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.docker.compose.service.connection.elasticsearch; + +import java.util.Map; + +class ElasticsearchEnvironment { + + private final String password; + + ElasticsearchEnvironment(Map env) { + this.password = env.get("ELASTIC_PASSWORD"); + } + + String getPassword() { + return this.password; + } + +} diff --git a/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index d6949d5de09..83a71aae869 100644 --- a/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -16,6 +16,7 @@ org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ org.springframework.ai.docker.compose.service.connection.chroma.ChromaDockerComposeConnectionDetailsFactory,\ +org.springframework.ai.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.mongo.MongoDbAtlasLocalDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.ollama.OllamaDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.opensearch.AwsOpenSearchDockerComposeConnectionDetailsFactory,\ diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIT.java new file mode 100644 index 00000000000..4816ed460aa --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIT.java @@ -0,0 +1,67 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.docker.compose.service.connection.elasticsearch; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIT; +import org.springframework.util.StreamUtils; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.Charset; + +import static org.assertj.core.api.Assertions.assertThat; + +class ElasticsearchDockerComposeConnectionDetailsFactoryIT extends AbstractDockerComposeIT { + + protected ElasticsearchDockerComposeConnectionDetailsFactoryIT() { + super("elasticsearch-compose.yaml", DockerImageName.parse(getLatestElasticsearch())); + } + + private static String getLatestElasticsearch() { + String imageName = "docker.elastic.co/elasticsearch/elasticsearch:"; + try { + URL url = new URL("https://artifacts.elastic.co/releases/stack.json"); + URLConnection connection = url.openConnection(); + try (InputStream is = connection.getInputStream()) { + String result = StreamUtils.copyToString(is, Charset.defaultCharset()); + JSONObject json = new JSONObject(result); + JSONArray releases = json.getJSONArray("releases"); + JSONObject latest = (JSONObject) releases.get(releases.length() - 1); + return imageName + latest.getString("version"); + } + } + catch (IOException e) { + throw new RuntimeException( + "Failed to retrieve latest Elasticsearch version from https://artifacts.elastic.co/releases/stack.json", + e); + } + } + + @Test + void runCreatesConnectionDetails() { + ElasticsearchConnectionDetails connectionDetails = run(ElasticsearchConnectionDetails.class); + assertThat(connectionDetails.getNodes()).isNotEmpty(); + } + +} diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchEnvironmentTests.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchEnvironmentTests.java new file mode 100644 index 00000000000..71501223127 --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/elasticsearch/ElasticsearchEnvironmentTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.docker.compose.service.connection.elasticsearch; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class ElasticsearchEnvironmentTests { + + @Test + void getPasswordWhenNoPassword() { + ElasticsearchEnvironment environment = new ElasticsearchEnvironment(Collections.emptyMap()); + assertThat(environment.getPassword()).isNull(); + } + + @Test + void getPasswordWhenHasPassword() { + ElasticsearchEnvironment environment = new ElasticsearchEnvironment(Map.of("ELASTIC_PASSWORD", "Ym9vcCE=")); + assertThat(environment.getPassword()).isEqualTo("Ym9vcCE="); + } + +} diff --git a/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml b/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml new file mode 100644 index 00000000000..7cd4447e925 --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml @@ -0,0 +1,9 @@ +services: + elasticsearch: + image: '{imageName}' + ports: + - '9200' + environment: + - ELASTIC_PASSWORD=Ym9vcCE= + - discovery.type=single-node + - ES_JAVA_OPTS=-Xms128m -Xmx512m diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml index 86b1c66477a..7e9d6f7bb09 100644 --- a/spring-ai-spring-boot-testcontainers/pom.xml +++ b/spring-ai-spring-boot-testcontainers/pom.xml @@ -62,6 +62,12 @@ ${project.parent.version} true + + org.springframework.ai + spring-ai-autoconfigure-vector-store-elasticsearch + ${project.parent.version} + true + org.springframework.ai spring-ai-autoconfigure-vector-store-mongodb-atlas @@ -151,6 +157,14 @@ true + + + org.springframework.ai + spring-ai-elasticsearch-store + ${project.parent.version} + true + + org.springframework.ai @@ -308,6 +322,13 @@ true + + org.testcontainers + elasticsearch + 1.21.3 + true + + org.testcontainers localstack diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java new file mode 100644 index 00000000000..b7b7eddc412 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java @@ -0,0 +1,69 @@ +package org.springframework.ai.testcontainers.service.connection.elasticsearch; + +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +import java.util.List; + +/** + * A {@link ContainerConnectionDetailsFactory} implementation that provides + * {@link ElasticsearchConnectionDetails} for a {@link ElasticsearchContainer}. + * + * @author Laura Trotta + * @see ContainerConnectionDetailsFactory + * @see ElasticsearchConnectionDetails + * @see ElasticsearchContainer + * @since 1.0.2 + */ +class ElasticsearchContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public ElasticsearchConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new ElasticsearchContainerConnectionDetails(source); + } + + /** + * {@link ElasticsearchConnectionDetails} backed by a + * {@link ContainerConnectionSource}. + */ + private static final class ElasticsearchContainerConnectionDetails + extends ContainerConnectionDetails implements ElasticsearchConnectionDetails { + + private ElasticsearchContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public List getNodes() { + return List.of(new Node(getContainer().getHttpHostAddress(), getContainer().getMappedPort(9200), + Node.Protocol.HTTP, getUsername(), getPassword())); + } + + @Override + public String getUsername() { + return "elastic"; + } + + @Override + public String getPassword() { + return "changeme"; + } + + @Override + public String getPathPrefix() { + return ""; + } + + @Override + public SslBundle getSslBundle() { + return super.getSslBundle(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 19576a7a6f4..0b2720e3ff6 100644 --- a/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -15,6 +15,7 @@ # org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ org.springframework.ai.testcontainers.service.connection.chroma.ChromaContainerConnectionDetailsFactory,\ +org.springframework.ai.testcontainers.service.connection.elasticsearch.ElasticsearchContainerConnectionDetailsFactory,\ org.springframework.ai.testcontainers.service.connection.milvus.MilvusContainerConnectionDetailsFactory,\ org.springframework.ai.testcontainers.service.connection.mongo.MongoDbAtlasLocalContainerConnectionDetailsFactory,\ org.springframework.ai.testcontainers.service.connection.ollama.OllamaContainerConnectionDetailsFactory,\ diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryIT.java new file mode 100644 index 00000000000..87577e8d766 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryIT.java @@ -0,0 +1,121 @@ +package org.springframework.ai.testcontainers.service.connection.elasticsearch; + +import org.apache.http.HttpHost; +import org.awaitility.Awaitility; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.Test; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.transformers.TransformersEmbeddingModel; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStore; +import org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStoreOptions; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasSize; + +@Testcontainers +class ElasticsearchContainerConnectionDetailsFactoryIT { + + @Container + @ServiceConnection + private static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer( + ElasticsearchImage.DEFAULT_IMAGE) + .withStartupTimeout(Duration.ofSeconds(30)) + .withEnv("ES_JAVA_OPTS", "-Xms256m -Xmx256m") + .withEnv("xpack.security.enabled", "false"); + + private final List documents = List.of( + new Document("1", getText("classpath:/test/data/spring.ai.txt"), Map.of("meta1", "meta1")), + new Document("2", getText("classpath:/test/data/time.shelter.txt"), Map.of()), + new Document("3", getText("classpath:/test/data/great.depression.txt"), Map.of("meta2", "meta2"))); + + @Test + public void addAndSearchTest() { + getContextRunner().run(context -> { + VectorStore vectorStore = context.getBean(VectorStore.class); + vectorStore.add(this.documents); + + Awaitility.await() + .until(() -> vectorStore.similaritySearch( + SearchRequest.builder().query("Great Depression").topK(1).similarityThreshold(0).build()), + hasSize(1)); + + List results = vectorStore.similaritySearch( + SearchRequest.builder().query("Great Depression").topK(1).similarityThreshold(0).build()); + + assertThat(results).hasSize(1); + Document resultDoc = results.get(0); + assertThat(resultDoc.getId()).isEqualTo(this.documents.get(2).getId()); + assertThat(resultDoc.getText()).contains("The Great Depression (1929–1939) was an economic " + "shock"); + assertThat(resultDoc.getMetadata()).hasSize(2); + assertThat(resultDoc.getMetadata()).containsKey("meta2"); + assertThat(resultDoc.getMetadata()).containsKey("distance"); + + // Remove all documents from the store + vectorStore.delete(this.documents.stream().map(Document::getId).toList()); + + Awaitility.await() + .until(() -> vectorStore.similaritySearch( + SearchRequest.builder().query("Great Depression").topK(1).similarityThreshold(0).build()), + hasSize(0)); + + }); + } + + private ApplicationContextRunner getContextRunner() { + return new ApplicationContextRunner().withUserConfiguration(Config.class); + } + + private String getText(String uri) { + var resource = new DefaultResourceLoader().getResource(uri); + try { + return resource.getContentAsString(StandardCharsets.UTF_8); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + public EmbeddingModel embeddingModel() { + return new TransformersEmbeddingModel(); + } + + @Bean + RestClient restClient() { + return RestClient.builder(new HttpHost(elasticsearch.getHost(), elasticsearch.getMappedPort(9200), "http")) + .build(); + } + + @Bean + public ElasticsearchVectorStore vectorStoreDefault(EmbeddingModel embeddingModel, RestClient restClient) { + ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions(); + options.setDimensions(384); + return ElasticsearchVectorStore.builder(restClient, embeddingModel) + .initializeSchema(true) + .options(options) + .build(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchImage.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchImage.java new file mode 100644 index 00000000000..9eca6a75017 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/elasticsearch/ElasticsearchImage.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.testcontainers.service.connection.elasticsearch; + +import org.testcontainers.utility.DockerImageName; + +/** + * @author Laura Trotta + */ +public final class ElasticsearchImage { + + public static final DockerImageName DEFAULT_IMAGE = DockerImageName + .parse("docker.elastic.co/elasticsearch/elasticsearch:9.1.2"); + + private ElasticsearchImage() { + + } + +}