diff --git a/start-site/src/main/java/io/spring/start/site/container/SimpleDockerServiceResolver.java b/start-site/src/main/java/io/spring/start/site/container/SimpleDockerServiceResolver.java index 8e75364f5b..e7c9928132 100644 --- a/start-site/src/main/java/io/spring/start/site/container/SimpleDockerServiceResolver.java +++ b/start-site/src/main/java/io/spring/start/site/container/SimpleDockerServiceResolver.java @@ -38,20 +38,25 @@ public SimpleDockerServiceResolver() { this.dockerServices.put("activeMQClassic", activeMQClassic()); this.dockerServices.put("artemis", artemis()); this.dockerServices.put("cassandra", cassandra()); + this.dockerServices.put("chroma", chroma()); this.dockerServices.put("elasticsearch", elasticsearch()); this.dockerServices.put("kafka", kafka()); this.dockerServices.put("mariaDb", mariaDb()); + this.dockerServices.put("milvus", milvus()); this.dockerServices.put("mongoDb", mongoDb()); this.dockerServices.put("mysql", mysql()); this.dockerServices.put("neo4j", neo4j()); + this.dockerServices.put("ollama", ollama()); this.dockerServices.put("oracleFree", oracleFree()); this.dockerServices.put("pgvector", pgvector()); this.dockerServices.put("postgres", postgres()); this.dockerServices.put("pulsar", pulsar()); + this.dockerServices.put("qdrant", qdrant()); this.dockerServices.put("rabbit", rabbit()); this.dockerServices.put("redis", redis()); this.dockerServices.put("redisStack", redisStack()); this.dockerServices.put("sqlServer", sqlServer()); + this.dockerServices.put("weaviate", weaviate()); this.dockerServices.put("zipkin", zipkin()); } @@ -83,6 +88,13 @@ private static DockerService cassandra() { .build(); } + private static DockerService chroma() { + return DockerService.withImageAndTag("chromadb/chroma") + .website("https://hub.docker.com/r/chromadb/chroma") + .ports(8000) + .build(); + } + private static DockerService elasticsearch() { // They don't provide a 'latest' tag return DockerService.withImageAndTag("docker.elastic.co/elasticsearch/elasticsearch:7.17.10") @@ -102,6 +114,13 @@ private static DockerService mariaDb() { return DockerService.withImageAndTag("mariadb").website("https://hub.docker.com/_/mariadb").ports(3306).build(); } + private static DockerService milvus() { + return DockerService.withImageAndTag("milvusdb/milvus") + .website("https://hub.docker.com/r/milvusdb/milvus") + .ports(19530) + .build(); + } + private static DockerService mongoDb() { return DockerService.withImageAndTag("mongo").website("https://hub.docker.com/_/mongo").ports(27017).build(); } @@ -114,6 +133,13 @@ private static DockerService neo4j() { return DockerService.withImageAndTag("neo4j").website("https://hub.docker.com/_/neo4j").ports(7687).build(); } + private static DockerService ollama() { + return DockerService.withImageAndTag("ollama/ollama") + .website("https://hub.docker.com/r/ollama/ollama") + .ports(11434) + .build(); + } + private static DockerService oracleFree() { return DockerService.withImageAndTag("gvenzl/oracle-free") .website("https://hub.docker.com/r/gvenzl/oracle-free") @@ -143,6 +169,13 @@ private static DockerService pulsar() { .build(); } + private static DockerService qdrant() { + return DockerService.withImageAndTag("qdrant/qdrant") + .website("https://hub.docker.com/r/qdrant/qdrant") + .ports(6334) + .build(); + } + private static DockerService rabbit() { return DockerService.withImageAndTag("rabbitmq") .website("https://hub.docker.com/_/rabbitmq") @@ -168,6 +201,13 @@ private static DockerService sqlServer() { .build(); } + private static DockerService weaviate() { + return DockerService.withImageAndTag("semitechnologies/weaviate") + .website("https://hub.docker.com/r/semitechnologies/weaviate") + .ports(8080) + .build(); + } + private static DockerService zipkin() { return DockerService.withImageAndTag("openzipkin/zipkin") .website("https://hub.docker.com/r/openzipkin/zipkin/") diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiChromaProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiChromaProjectGenerationConfiguration.java new file mode 100644 index 0000000000..129be44dbb --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiChromaProjectGenerationConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.start.site.container.ComposeFileCustomizer; +import io.spring.start.site.container.DockerServiceResolver; +import io.spring.start.site.container.ServiceConnections.ServiceConnection; +import io.spring.start.site.container.ServiceConnectionsCustomizer; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Chroma. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("spring-ai-vectordb-chroma") +class SpringAiChromaProjectGenerationConfiguration { + + private static final String TESTCONTAINERS_CLASS_NAME = "org.testcontainers.chromadb.ChromaDBContainer"; + + @Bean + @ConditionalOnRequestedDependency("testcontainers") + ServiceConnectionsCustomizer chromaServiceConnectionsCustomizer(InitializrMetadata metadata, + ProjectDescription description, DockerServiceResolver serviceResolver) { + return (serviceConnections) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("chroma", (service) -> serviceConnections + .addServiceConnection(ServiceConnection.ofContainer("chroma", service, TESTCONTAINERS_CLASS_NAME))); + } + }; + } + + @Bean + @ConditionalOnRequestedDependency("docker-compose") + ComposeFileCustomizer chromaComposeFileCustomizer(InitializrMetadata metadata, ProjectDescription description, + DockerServiceResolver serviceResolver) { + return (composeFile) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("chroma", (service) -> composeFile.services().add("chroma", service)); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiDockerComposeProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiDockerComposeProjectGenerationConfiguration.java new file mode 100644 index 0000000000..07241d26f4 --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiDockerComposeProjectGenerationConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.buildsystem.Build; +import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.buildsystem.DependencyScope; +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.generator.spring.build.BuildCustomizer; +import io.spring.initializr.metadata.InitializrMetadata; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Spring AI Docker Compose. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("docker-compose") +class SpringAiDockerComposeProjectGenerationConfiguration { + + @Bean + BuildCustomizer springAiDockerComposeBuildCustomizer(InitializrMetadata metadata, + ProjectDescription description) { + return (build) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + build.dependencies() + .add("spring-ai-docker-compose", + Dependency.withCoordinates("org.springframework.ai", "spring-ai-spring-boot-docker-compose") + .scope(DependencyScope.TEST_COMPILE)); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiMilvusProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiMilvusProjectGenerationConfiguration.java new file mode 100644 index 0000000000..80973d72f5 --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiMilvusProjectGenerationConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.start.site.container.DockerServiceResolver; +import io.spring.start.site.container.ServiceConnections.ServiceConnection; +import io.spring.start.site.container.ServiceConnectionsCustomizer; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Milvus. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("spring-ai-vectordb-milvus") +class SpringAiMilvusProjectGenerationConfiguration { + + private static final String TESTCONTAINERS_CLASS_NAME = "org.testcontainers.milvus.MilvusContainer"; + + @Bean + @ConditionalOnRequestedDependency("testcontainers") + ServiceConnectionsCustomizer milvusServiceConnectionsCustomizer(InitializrMetadata metadata, + ProjectDescription description, DockerServiceResolver serviceResolver) { + return (serviceConnections) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("milvus", (service) -> serviceConnections + .addServiceConnection(ServiceConnection.ofContainer("milvus", service, TESTCONTAINERS_CLASS_NAME))); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiOllamaProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiOllamaProjectGenerationConfiguration.java new file mode 100644 index 0000000000..fea79e3092 --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiOllamaProjectGenerationConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.start.site.container.ComposeFileCustomizer; +import io.spring.start.site.container.DockerServiceResolver; +import io.spring.start.site.container.ServiceConnections.ServiceConnection; +import io.spring.start.site.container.ServiceConnectionsCustomizer; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Ollama. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("spring-ai-ollama") +class SpringAiOllamaProjectGenerationConfiguration { + + private static final String TESTCONTAINERS_CLASS_NAME = "org.testcontainers.ollama.OllamaContainer"; + + @Bean + @ConditionalOnRequestedDependency("testcontainers") + ServiceConnectionsCustomizer ollamaServiceConnectionsCustomizer(InitializrMetadata metadata, + ProjectDescription description, DockerServiceResolver serviceResolver) { + return (serviceConnections) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("ollama", (service) -> serviceConnections + .addServiceConnection(ServiceConnection.ofContainer("ollama", service, TESTCONTAINERS_CLASS_NAME))); + } + }; + } + + @Bean + @ConditionalOnRequestedDependency("docker-compose") + ComposeFileCustomizer ollamaComposeFileCustomizer(InitializrMetadata metadata, ProjectDescription description, + DockerServiceResolver serviceResolver) { + return (composeFile) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("ollama", (service) -> composeFile.services().add("ollama", service)); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiQdrantProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiQdrantProjectGenerationConfiguration.java new file mode 100644 index 0000000000..a9a4004980 --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiQdrantProjectGenerationConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.start.site.container.ComposeFileCustomizer; +import io.spring.start.site.container.DockerServiceResolver; +import io.spring.start.site.container.ServiceConnections.ServiceConnection; +import io.spring.start.site.container.ServiceConnectionsCustomizer; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Qdrant. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("spring-ai-vectordb-qdrant") +class SpringAiQdrantProjectGenerationConfiguration { + + private static final String TESTCONTAINERS_CLASS_NAME = "org.testcontainers.qdrant.QdrantContainer"; + + @Bean + @ConditionalOnRequestedDependency("testcontainers") + ServiceConnectionsCustomizer qdrantServiceConnectionsCustomizer(InitializrMetadata metadata, + ProjectDescription description, DockerServiceResolver serviceResolver) { + return (serviceConnections) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("qdrant", (service) -> serviceConnections + .addServiceConnection(ServiceConnection.ofContainer("qdrant", service, TESTCONTAINERS_CLASS_NAME))); + } + }; + } + + @Bean + @ConditionalOnRequestedDependency("docker-compose") + ComposeFileCustomizer qdrantComposeFileCustomizer(InitializrMetadata metadata, ProjectDescription description, + DockerServiceResolver serviceResolver) { + return (composeFile) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("qdrant", (service) -> composeFile.services().add("qdrant", service)); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiTestcontainersProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiTestcontainersProjectGenerationConfiguration.java new file mode 100644 index 0000000000..1df9e88b2f --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiTestcontainersProjectGenerationConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.buildsystem.Build; +import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.buildsystem.DependencyScope; +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.generator.spring.build.BuildCustomizer; +import io.spring.initializr.metadata.InitializrMetadata; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Spring AI Testcontainers. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("testcontainers") +class SpringAiTestcontainersProjectGenerationConfiguration { + + @Bean + BuildCustomizer springAiTestcontainersBuildCustomizer(InitializrMetadata metadata, + ProjectDescription description) { + return (build) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + build.dependencies() + .add("spring-ai-testcontainers", + Dependency.withCoordinates("org.springframework.ai", "spring-ai-spring-boot-testcontainers") + .scope(DependencyScope.TEST_COMPILE)); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiVersion.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiVersion.java new file mode 100644 index 0000000000..38b681c060 --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiVersion.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.generator.version.VersionParser; +import io.spring.initializr.generator.version.VersionRange; +import io.spring.initializr.metadata.InitializrMetadata; + +final class SpringAiVersion { + + private static final VersionRange SPRING_AI_1_0_0_OR_LATER = VersionParser.DEFAULT.parseRange("1.0.0-M2"); + + private SpringAiVersion() { + + } + + static boolean version1OrLater(InitializrMetadata metadata, Version platformVersion) { + var springAiBomVersion = metadata.getConfiguration() + .getEnv() + .getBoms() + .get("spring-ai") + .resolve(platformVersion) + .getVersion(); + return SPRING_AI_1_0_0_OR_LATER.match(Version.parse(springAiBomVersion)); + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiWeaviateProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiWeaviateProjectGenerationConfiguration.java new file mode 100644 index 0000000000..b6f4c63e15 --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/SpringAiWeaviateProjectGenerationConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationConfiguration; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.start.site.container.ComposeFileCustomizer; +import io.spring.start.site.container.DockerServiceResolver; +import io.spring.start.site.container.ServiceConnections.ServiceConnection; +import io.spring.start.site.container.ServiceConnectionsCustomizer; + +import org.springframework.context.annotation.Bean; + +/** + * Configuration for generation of projects that depend on Weaviate. + * + * @author Eddú Meléndez + */ +@ProjectGenerationConfiguration +@ConditionalOnRequestedDependency("spring-ai-vectordb-weaviate") +class SpringAiWeaviateProjectGenerationConfiguration { + + private static final String TESTCONTAINERS_CLASS_NAME = "org.testcontainers.weaviate.WeaviateContainer"; + + @Bean + @ConditionalOnRequestedDependency("testcontainers") + ServiceConnectionsCustomizer weaviateServiceConnectionsCustomizer(InitializrMetadata metadata, + ProjectDescription description, DockerServiceResolver serviceResolver) { + return (serviceConnections) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("weaviate", (service) -> serviceConnections.addServiceConnection( + ServiceConnection.ofContainer("weaviate", service, TESTCONTAINERS_CLASS_NAME))); + } + }; + } + + @Bean + @ConditionalOnRequestedDependency("docker-compose") + ComposeFileCustomizer weaviateComposeFileCustomizer(InitializrMetadata metadata, ProjectDescription description, + DockerServiceResolver serviceResolver) { + return (composeFile) -> { + if (SpringAiVersion.version1OrLater(metadata, description.getPlatformVersion())) { + serviceResolver.doWith("weaviate", (service) -> composeFile.services().add("weaviate", service)); + } + }; + } + +} diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/package-info.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/package-info.java new file mode 100644 index 0000000000..94f77d1eaa --- /dev/null +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springai/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-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. + */ + +/** + * Extensions for generation of projects that depend on Spring AI. + */ +package io.spring.start.site.extension.dependency.springai; diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersModuleRegistry.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersModuleRegistry.java index 2fa551e2cb..334b564111 100644 --- a/start-site/src/main/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersModuleRegistry.java +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersModuleRegistry.java @@ -42,6 +42,8 @@ abstract class TestcontainersModuleRegistry { private static final VersionRange SPRING_BOOT_3_3_0_M2_OR_LATER = VersionParser.DEFAULT.parseRange("3.3.0-M2"); + private static final VersionRange SPRING_BOOT_3_3_0_M3_OR_LATER = VersionParser.DEFAULT.parseRange("3.3.0-M3"); + static Iterable create(Version platformVersion) { List builders = new ArrayList<>(); if (SPRING_BOOT_3_3_0_M2_OR_LATER.match(platformVersion)) { @@ -89,6 +91,18 @@ static Iterable create(Version platformVersion) { .customizeHelpDocument(addReferenceLink("Solace Module", "solace/"))); builders.add(onDependencies("sqlserver").customizeBuild(addModule("mssqlserver")) .customizeHelpDocument(addReferenceLink("MS SQL Server Module", "databases/mssqlserver/"))); + if (SPRING_BOOT_3_3_0_M3_OR_LATER.match(platformVersion)) { + builders.add(onDependencies("spring-ai-vectordb-chroma").customizeBuild(addModule("chromadb")) + .customizeHelpDocument(addReferenceLink("Chroma Module", "testcontainers/"))); + builders.add(onDependencies("spring-ai-vectordb-milvus").customizeBuild(addModule("milvus")) + .customizeHelpDocument(addReferenceLink("Milvus Module", "testcontainers/"))); + builders.add(onDependencies("spring-ai-ollama").customizeBuild(addModule("ollama")) + .customizeHelpDocument(addReferenceLink("Ollama Module", "testcontainers/"))); + builders.add(onDependencies("spring-ai-vectordb-qdrant").customizeBuild(addModule("qdrant")) + .customizeHelpDocument(addReferenceLink("Qdrant Module", "testcontainers/"))); + builders.add(onDependencies("spring-ai-vectordb-weaviate").customizeBuild(addModule("weaviate")) + .customizeHelpDocument(addReferenceLink("Weaviate Module", "testcontainers/"))); + } return builders.stream().map(Builder::build).collect(Collectors.toList()); } diff --git a/start-site/src/main/resources/META-INF/spring.factories b/start-site/src/main/resources/META-INF/spring.factories index d5c145a717..da59fbe1f5 100644 --- a/start-site/src/main/resources/META-INF/spring.factories +++ b/start-site/src/main/resources/META-INF/spring.factories @@ -27,6 +27,13 @@ io.spring.start.site.extension.dependency.redis.RedisProjectGenerationConfigurat io.spring.start.site.extension.dependency.redis.RedisStackProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.sbom.SbomProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.solace.SolaceProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiChromaProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiDockerComposeProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiMilvusProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiOllamaProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiQdrantProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiTestcontainersProjectGenerationConfiguration,\ +io.spring.start.site.extension.dependency.springai.SpringAiWeaviateProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springamqp.SpringAmqpProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springazure.SpringAzureProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springboot.SpringBootProjectGenerationConfiguration,\ diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiChromaProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiChromaProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..84549936da --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiChromaProjectGenerationConfigurationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringAiChromaProjectGenerationConfiguration} + * + * @author Eddú Meléndez + */ +class SpringAiChromaProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @Test + void doesNothingWithoutDockerCompose() { + ProjectRequest request = createProjectRequest("web", "spring-ai-vectordb-chroma"); + ProjectStructure structure = generateProject(request); + assertThat(structure.getProjectDirectory().resolve("compose.yaml")).doesNotExist(); + } + + @Test + void createsChromaService() { + ProjectRequest request = createProjectRequest("docker-compose", "spring-ai-vectordb-chroma"); + assertThat(composeFile(request)).hasSameContentAs(new ClassPathResource("compose/chroma.yaml")); + } + +} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiDockerComposeProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiDockerComposeProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..587a1e247b --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiDockerComposeProjectGenerationConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import java.util.stream.Stream; + +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringAiDockerComposeProjectGenerationConfiguration}. + * + * @author Eddú Meléndez + */ +class SpringAiDockerComposeProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @ParameterizedTest + @MethodSource("supportedDockerComposeSpringAiEntriesBuild") + void springAiTestcontainersDependencyIsAdded(String springAiDependency) { + ProjectRequest projectRequest = createProject("3.3.0", "docker-compose", springAiDependency); + assertThat(mavenPom(projectRequest)).hasDependency("org.springframework.ai", + "spring-ai-spring-boot-docker-compose", null, "test"); + } + + private ProjectRequest createProject(String springBootVersion, String... styles) { + ProjectRequest projectRequest = createProjectRequest(styles); + projectRequest.setLanguage("java"); + projectRequest.setBootVersion(springBootVersion); + return projectRequest; + } + + static Stream supportedDockerComposeSpringAiEntriesBuild() { + return Stream.of("spring-ai-vectordb-chroma", "spring-ai-vectordb-milvus", "spring-ai-ollama", + "spring-ai-vectordb-qdrant", "spring-ai-vectordb-weaviate"); + } + +} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiOllamaProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiOllamaProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..aab0aa859d --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiOllamaProjectGenerationConfigurationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringAiOllamaProjectGenerationConfiguration} + * + * @author Eddú Meléndez + */ +class SpringAiOllamaProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @Test + void doesNothingWithoutDockerCompose() { + ProjectRequest request = createProjectRequest("web", "spring-ai-ollama"); + ProjectStructure structure = generateProject(request); + assertThat(structure.getProjectDirectory().resolve("compose.yaml")).doesNotExist(); + } + + @Test + void createsOllamaService() { + ProjectRequest request = createProjectRequest("docker-compose", "spring-ai-ollama"); + assertThat(composeFile(request)).hasSameContentAs(new ClassPathResource("compose/ollama.yaml")); + } + +} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiQdrantProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiQdrantProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..aea6c230f9 --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiQdrantProjectGenerationConfigurationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringAiQdrantProjectGenerationConfiguration} + * + * @author Eddú Meléndez + */ +class SpringAiQdrantProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @Test + void doesNothingWithoutDockerCompose() { + ProjectRequest request = createProjectRequest("web", "spring-ai-vectordb-qdrant"); + ProjectStructure structure = generateProject(request); + assertThat(structure.getProjectDirectory().resolve("compose.yaml")).doesNotExist(); + } + + @Test + void createsQdrantService() { + ProjectRequest request = createProjectRequest("docker-compose", "spring-ai-vectordb-qdrant"); + assertThat(composeFile(request)).hasSameContentAs(new ClassPathResource("compose/qdrant.yaml")); + } + +} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiTestcontainersProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiTestcontainersProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..a63b1ad8df --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiTestcontainersProjectGenerationConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import java.util.stream.Stream; + +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringAiTestcontainersProjectGenerationConfiguration}. + * + * @author Eddú Meléndez + */ +class SpringAiTestcontainersProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @ParameterizedTest + @MethodSource("supportedTestcontainersSpringAiEntriesBuild") + void springAiTestcontainersDependencyIsAdded(String springAiDependency) { + ProjectRequest projectRequest = createProject("3.3.0", "testcontainers", springAiDependency); + assertThat(mavenPom(projectRequest)).hasDependency("org.springframework.ai", + "spring-ai-spring-boot-testcontainers", null, "test"); + } + + private ProjectRequest createProject(String springBootVersion, String... styles) { + ProjectRequest projectRequest = createProjectRequest(styles); + projectRequest.setLanguage("java"); + projectRequest.setBootVersion(springBootVersion); + return projectRequest; + } + + static Stream supportedTestcontainersSpringAiEntriesBuild() { + return Stream.of("spring-ai-vectordb-chroma", "spring-ai-vectordb-milvus", "spring-ai-ollama", + "spring-ai-vectordb-qdrant", "spring-ai-vectordb-weaviate"); + } + +} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiWeaviateProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiWeaviateProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..8560dd744e --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springai/SpringAiWeaviateProjectGenerationConfigurationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-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 io.spring.start.site.extension.dependency.springai; + +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringAiWeaviateProjectGenerationConfiguration} + * + * @author Eddú Meléndez + */ +class SpringAiWeaviateProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @Test + void doesNothingWithoutDockerCompose() { + ProjectRequest request = createProjectRequest("web", "spring-ai-vectordb-qdrant"); + ProjectStructure structure = generateProject(request); + assertThat(structure.getProjectDirectory().resolve("compose.yaml")).doesNotExist(); + } + + @Test + void createsWeaviateService() { + ProjectRequest request = createProjectRequest("docker-compose", "spring-ai-vectordb-weaviate"); + assertThat(composeFile(request)).hasSameContentAs(new ClassPathResource("compose/weaviate.yaml")); + } + +} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersProjectGenerationConfigurationTests.java index 6186931933..f9421a31b0 100644 --- a/start-site/src/test/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersProjectGenerationConfigurationTests.java +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/testcontainers/TestcontainersProjectGenerationConfigurationTests.java @@ -71,6 +71,17 @@ void buildWithSpringBoot33AndTestcontainersActiveMQModule(String springBootDepen .hasDependency(getDependency("testcontainers")); } + @ParameterizedTest + @MethodSource("supportedTestcontainersSpringAiEntriesBuild") + void buildWithSpringBoot33M3AndTestcontainersSpringAiModule(String springBootDependencyId, + String testcontainersArtifactId) { + assertThat(generateProject(SPRING_BOOT_VERSION_3_3, "testcontainers", springBootDependencyId)).mavenBuild() + .doesNotHaveBom("org.testcontainers", "testcontainers-bom") + .hasDependency(getDependency(springBootDependencyId).resolve(Version.parse("3.3.0"))) + .hasDependency("org.testcontainers", testcontainersArtifactId, null, "test") + .hasDependency(getDependency("testcontainers")); + } + static Stream supportedEntriesBuild320() { return Stream.of(Arguments.arguments("amqp", "rabbitmq"), Arguments.of("amqp-streams", "rabbitmq"), Arguments.arguments("cloud-gcp", "gcloud"), Arguments.arguments("cloud-gcp-pubsub", "gcloud"), @@ -97,6 +108,13 @@ static Stream supportedTestcontainersActiveMQEntriesBuild() { return Stream.of(Arguments.arguments("activemq", "activemq"), Arguments.arguments("artemis", "activemq")); } + static Stream supportedTestcontainersSpringAiEntriesBuild() { + return Stream.of(Arguments.arguments("spring-ai-vectordb-chroma", "chromadb"), + Arguments.arguments("spring-ai-vectordb-milvus", "milvus"), + Arguments.arguments("spring-ai-vectordb-qdrant", "qdrant"), + Arguments.arguments("spring-ai-vectordb-weaviate", "weaviate")); + } + @ParameterizedTest @MethodSource("supportedEntriesHelpDocument") void linkToSupportedEntriesWhenTestContainerIsPresentIsAdded(String dependencyId, String docHref) { diff --git a/start-site/src/test/resources/compose/chroma.yaml b/start-site/src/test/resources/compose/chroma.yaml new file mode 100644 index 0000000000..9f00255351 --- /dev/null +++ b/start-site/src/test/resources/compose/chroma.yaml @@ -0,0 +1,5 @@ +services: + chroma: + image: 'chromadb/chroma:latest' + ports: + - '8000' diff --git a/start-site/src/test/resources/compose/ollama.yaml b/start-site/src/test/resources/compose/ollama.yaml new file mode 100644 index 0000000000..60ccf775b2 --- /dev/null +++ b/start-site/src/test/resources/compose/ollama.yaml @@ -0,0 +1,5 @@ +services: + ollama: + image: 'ollama/ollama:latest' + ports: + - '11434' diff --git a/start-site/src/test/resources/compose/qdrant.yaml b/start-site/src/test/resources/compose/qdrant.yaml new file mode 100644 index 0000000000..01613ae2fd --- /dev/null +++ b/start-site/src/test/resources/compose/qdrant.yaml @@ -0,0 +1,5 @@ +services: + qdrant: + image: 'qdrant/qdrant:latest' + ports: + - '6334' diff --git a/start-site/src/test/resources/compose/weaviate.yaml b/start-site/src/test/resources/compose/weaviate.yaml new file mode 100644 index 0000000000..4dac3a88bb --- /dev/null +++ b/start-site/src/test/resources/compose/weaviate.yaml @@ -0,0 +1,5 @@ +services: + weaviate: + image: 'semitechnologies/weaviate:latest' + ports: + - '8080'