From c7f9b5c7c6f1f25a6abf9b963c071625500a40e3 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Thu, 7 Sep 2023 19:55:19 -0500 Subject: [PATCH 1/3] Add optional 'command' to Docker compose service Depends on https://github.com/spring-io/initializr/pull/1461 --- .../start/site/container/DockerService.java | 20 +++++++++++-- .../SimpleDockerServiceResolver.java | 28 ++++++++++--------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/start-site/src/main/java/io/spring/start/site/container/DockerService.java b/start-site/src/main/java/io/spring/start/site/container/DockerService.java index d7ec540b981..09cb2a33a90 100644 --- a/start-site/src/main/java/io/spring/start/site/container/DockerService.java +++ b/start-site/src/main/java/io/spring/start/site/container/DockerService.java @@ -24,6 +24,7 @@ * Description of a Docker service. * * @author Stephane Nicoll + * @author Chris Bono */ public class DockerService implements Consumer { @@ -33,12 +34,15 @@ public class DockerService implements Consumer { private final String website; + private final String command; + private final int[] ports; - DockerService(String image, String imageTag, String website, int... ports) { + DockerService(String image, String imageTag, String website, String command, int... ports) { this.image = image; this.imageTag = imageTag; this.website = website; + this.command = command; this.ports = ports; } @@ -67,6 +71,14 @@ public String getWebsite() { return this.website; } + /** + * Return the command to use to override the default command (optional). + * @return the command + */ + public String getCommand() { + return this.command; + } + /** * Return the ports that should be exposed by the service. * @return the ports to expose @@ -77,7 +89,11 @@ public int[] getPorts() { @Override public void accept(Builder builder) { - builder.image(this.image).imageTag(this.imageTag).imageWebsite(this.website).ports(this.ports); + builder.image(this.image) + .imageTag(this.imageTag) + .imageWebsite(this.website) + .ports(this.ports) + .command(this.command); } } 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 f81853f084b..126baa053d3 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 @@ -48,59 +48,61 @@ public SimpleDockerServiceResolver() { } private static DockerService activeMQ() { - return new DockerService("symptoma/activemq", "latest", "https://hub.docker.com/r/symptoma/activemq", 61616); + return new DockerService("symptoma/activemq", "latest", "https://hub.docker.com/r/symptoma/activemq", null, + 61616); } private static DockerService cassandra() { - return new DockerService("cassandra", "latest", "https://hub.docker.com/_/cassandra", 9042); + return new DockerService("cassandra", "latest", "https://hub.docker.com/_/cassandra", null, 9042); } private static DockerService elasticsearch() { // They don't provide a 'latest' tag return new DockerService("docker.elastic.co/elasticsearch/elasticsearch", "7.17.10", - "https://www.docker.elastic.co/r/elasticsearch", 9200, 9300); + "https://www.docker.elastic.co/r/elasticsearch", null, 9200, 9300); } private static DockerService kafka() { return new DockerService("confluentinc/cp-kafka", "latest", "https://hub.docker.com/r/confluentinc/cp-kafka", - 9092); + null, 9092); } private static DockerService mariaDb() { - return new DockerService("mariadb", "latest", "https://hub.docker.com/_/mariadb", 3306); + return new DockerService("mariadb", "latest", "https://hub.docker.com/_/mariadb", null, 3306); } private static DockerService mongoDb() { - return new DockerService("mongo", "latest", "https://hub.docker.com/_/mongo", 27017); + return new DockerService("mongo", "latest", "https://hub.docker.com/_/mongo", null, 27017); } private static DockerService mysql() { - return new DockerService("mysql", "latest", "https://hub.docker.com/_/mysql", 3306); + return new DockerService("mysql", "latest", "https://hub.docker.com/_/mysql", null, 3306); } private static DockerService oracle() { - return new DockerService("gvenzl/oracle-xe", "latest", "https://hub.docker.com/r/gvenzl/oracle-xe", 1521); + return new DockerService("gvenzl/oracle-xe", "latest", "https://hub.docker.com/r/gvenzl/oracle-xe", null, 1521); } private static DockerService postgres() { - return new DockerService("postgres", "latest", "https://hub.docker.com/_/postgres", 5432); + return new DockerService("postgres", "latest", "https://hub.docker.com/_/postgres", null, 5432); } private static DockerService rabbit() { - return new DockerService("rabbitmq", "latest", "https://hub.docker.com/_/rabbitmq", 5672); + return new DockerService("rabbitmq", "latest", "https://hub.docker.com/_/rabbitmq", null, 5672); } private static DockerService redis() { - return new DockerService("redis", "latest", "https://hub.docker.com/_/redis", 6379); + return new DockerService("redis", "latest", "https://hub.docker.com/_/redis", null, 6379); } private static DockerService sqlServer() { return new DockerService("mcr.microsoft.com/mssql/server", "latest", - "https://mcr.microsoft.com/en-us/product/mssql/server/about/", 1433); + "https://mcr.microsoft.com/en-us/product/mssql/server/about/", null, 1433); } private static DockerService zipkin() { - return new DockerService("openzipkin/zipkin", "latest", "https://hub.docker.com/r/openzipkin/zipkin/", 9411); + return new DockerService("openzipkin/zipkin", "latest", "https://hub.docker.com/r/openzipkin/zipkin/", null, + 9411); } @Override From 8387b2de4b4f02cd8ab6c2fadc548038c79166b9 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Fri, 8 Sep 2023 14:45:54 -0500 Subject: [PATCH 2/3] Introduce builder Docker compose service Depends on https://github.com/spring-io/initializr/pull/1461 --- .../start/site/container/DockerService.java | 95 ++++++++++++++++--- .../SimpleDockerServiceResolver.java | 59 ++++++++---- .../site/container/DockerServiceTests.java | 77 +++++++++++++++ 3 files changed, 202 insertions(+), 29 deletions(-) create mode 100644 start-site/src/test/java/io/spring/start/site/container/DockerServiceTests.java diff --git a/start-site/src/main/java/io/spring/start/site/container/DockerService.java b/start-site/src/main/java/io/spring/start/site/container/DockerService.java index 09cb2a33a90..cece024c30c 100644 --- a/start-site/src/main/java/io/spring/start/site/container/DockerService.java +++ b/start-site/src/main/java/io/spring/start/site/container/DockerService.java @@ -16,9 +16,13 @@ package io.spring.start.site.container; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.TreeSet; import java.util.function.Consumer; -import io.spring.initializr.generator.container.docker.compose.ComposeService.Builder; +import io.spring.initializr.generator.container.docker.compose.ComposeService; /** * Description of a Docker service. @@ -26,7 +30,7 @@ * @author Stephane Nicoll * @author Chris Bono */ -public class DockerService implements Consumer { +public final class DockerService implements Consumer { private final String image; @@ -38,12 +42,21 @@ public class DockerService implements Consumer { private final int[] ports; - DockerService(String image, String imageTag, String website, String command, int... ports) { - this.image = image; - this.imageTag = imageTag; - this.website = website; - this.command = command; - this.ports = ports; + private DockerService(DockerService.Builder builder) { + this.image = builder.image; + this.imageTag = builder.imageTag; + this.website = builder.website; + this.command = builder.command; + this.ports = builder.ports.stream().mapToInt(Number::intValue).toArray(); + } + + /** + * Return a new builder using the specified image and optional tag. + * @param imageAndTag the image (and optional tag) to use for the service + * @return the new builder instance. + */ + public static DockerService.Builder withImageAndTag(String imageAndTag) { + return new DockerService.Builder(imageAndTag); } /** @@ -88,12 +101,72 @@ public int[] getPorts() { } @Override - public void accept(Builder builder) { + public void accept(ComposeService.Builder builder) { builder.image(this.image) .imageTag(this.imageTag) .imageWebsite(this.website) - .ports(this.ports) - .command(this.command); + .command(this.command) + .ports(this.ports); + } + + /** + * Builder for {@link DockerService}. + */ + public static class Builder { + + private String image; + + private String imageTag = "latest"; + + private String website; + + private String command; + + private final Set ports = new TreeSet<>(); + + protected Builder(String imageAndTag) { + String[] split = imageAndTag.split(":", 2); + String tag = (split.length == 1) ? "latest" : split[1]; + image(split[0]).imageTag(tag); + } + + public DockerService.Builder image(String image) { + this.image = image; + return this; + } + + public DockerService.Builder imageTag(String imageTag) { + this.imageTag = imageTag; + return this; + } + + public DockerService.Builder website(String website) { + this.website = website; + return this; + } + + public DockerService.Builder command(String command) { + this.command = command; + return this; + } + + public DockerService.Builder ports(Collection ports) { + this.ports.addAll(ports); + return this; + } + + public DockerService.Builder ports(int... ports) { + return ports(Arrays.stream(ports).boxed().toList()); + } + + /** + * Builds the {@link DockerService} instance. + * @return the built instance + */ + public DockerService build() { + return new DockerService(this); + } + } } 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 126baa053d3..7efbf0cdbb7 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 @@ -25,6 +25,7 @@ * * @author Stephane Nicoll * @author Moritz Halbritter + * @author Chris Bono */ public class SimpleDockerServiceResolver implements DockerServiceResolver { @@ -48,61 +49,83 @@ public SimpleDockerServiceResolver() { } private static DockerService activeMQ() { - return new DockerService("symptoma/activemq", "latest", "https://hub.docker.com/r/symptoma/activemq", null, - 61616); + return DockerService.withImageAndTag("symptoma/activemq") + .website("https://hub.docker.com/r/symptoma/activemq") + .ports(61616) + .build(); } private static DockerService cassandra() { - return new DockerService("cassandra", "latest", "https://hub.docker.com/_/cassandra", null, 9042); + return DockerService.withImageAndTag("cassandra") + .website("https://hub.docker.com/_/cassandra") + .ports(9042) + .build(); } private static DockerService elasticsearch() { // They don't provide a 'latest' tag - return new DockerService("docker.elastic.co/elasticsearch/elasticsearch", "7.17.10", - "https://www.docker.elastic.co/r/elasticsearch", null, 9200, 9300); + return DockerService.withImageAndTag("docker.elastic.co/elasticsearch/elasticsearch:7.17.10") + .website("https://www.docker.elastic.co/r/elasticsearch") + .ports(9200, 9300) + .build(); } private static DockerService kafka() { - return new DockerService("confluentinc/cp-kafka", "latest", "https://hub.docker.com/r/confluentinc/cp-kafka", - null, 9092); + return DockerService.withImageAndTag("confluentinc/cp-kafka") + .website("https://hub.docker.com/r/confluentinc/cp-kafka") + .ports(9092) + .build(); } private static DockerService mariaDb() { - return new DockerService("mariadb", "latest", "https://hub.docker.com/_/mariadb", null, 3306); + return DockerService.withImageAndTag("mariadb").website("https://hub.docker.com/_/mariadb").ports(3306).build(); } private static DockerService mongoDb() { - return new DockerService("mongo", "latest", "https://hub.docker.com/_/mongo", null, 27017); + return DockerService.withImageAndTag("mongo").website("https://hub.docker.com/_/mongo").ports(27017).build(); } private static DockerService mysql() { - return new DockerService("mysql", "latest", "https://hub.docker.com/_/mysql", null, 3306); + return DockerService.withImageAndTag("mysql").website("https://hub.docker.com/_/mysql").ports(3306).build(); } private static DockerService oracle() { - return new DockerService("gvenzl/oracle-xe", "latest", "https://hub.docker.com/r/gvenzl/oracle-xe", null, 1521); + return DockerService.withImageAndTag("gvenzl/oracle-xe") + .website("https://hub.docker.com/r/gvenzl/oracle-xe") + .ports(1521) + .build(); } private static DockerService postgres() { - return new DockerService("postgres", "latest", "https://hub.docker.com/_/postgres", null, 5432); + return DockerService.withImageAndTag("postgres") + .website("https://hub.docker.com/_/postgres") + .ports(5432) + .build(); } private static DockerService rabbit() { - return new DockerService("rabbitmq", "latest", "https://hub.docker.com/_/rabbitmq", null, 5672); + return DockerService.withImageAndTag("rabbitmq") + .website("https://hub.docker.com/_/rabbitmq") + .ports(5672) + .build(); } private static DockerService redis() { - return new DockerService("redis", "latest", "https://hub.docker.com/_/redis", null, 6379); + return DockerService.withImageAndTag("redis").website("https://hub.docker.com/_/redis").ports(6379).build(); } private static DockerService sqlServer() { - return new DockerService("mcr.microsoft.com/mssql/server", "latest", - "https://mcr.microsoft.com/en-us/product/mssql/server/about/", null, 1433); + return DockerService.withImageAndTag("mcr.microsoft.com/mssql/server") + .website("https://mcr.microsoft.com/en-us/product/mssql/server/about/") + .ports(1433) + .build(); } private static DockerService zipkin() { - return new DockerService("openzipkin/zipkin", "latest", "https://hub.docker.com/r/openzipkin/zipkin/", null, - 9411); + return DockerService.withImageAndTag("openzipkin/zipkin") + .website("https://hub.docker.com/r/openzipkin/zipkin/") + .ports(9411) + .build(); } @Override diff --git a/start-site/src/test/java/io/spring/start/site/container/DockerServiceTests.java b/start-site/src/test/java/io/spring/start/site/container/DockerServiceTests.java new file mode 100644 index 00000000000..1e961819dff --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/container/DockerServiceTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2023 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.container; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DockerService}. + * + * @author Chris Bono + */ +class DockerServiceTests { + + @Test + void builderWithMinimalOptions() { + DockerService service = DockerService.withImageAndTag("acme/toolbox").build(); + assertThat(service.getImage()).isEqualTo("acme/toolbox"); + assertThat(service.getImageTag()).isEqualTo("latest"); + assertThat(service.getWebsite()).isNull(); + assertThat(service.getCommand()).isNull(); + assertThat(service.getPorts()).isEmpty(); + } + + @Test + void builderWithAllOptions() { + DockerService service = DockerService.withImageAndTag("acme/toolbox") + .imageTag("1.0") + .website("acme/toolbox-dot-com") + .command("bin/acme run") + .ports(8007, 8008) + .build(); + assertThat(service.getImage()).isEqualTo("acme/toolbox"); + assertThat(service.getImageTag()).isEqualTo("1.0"); + assertThat(service.getWebsite()).isEqualTo("acme/toolbox-dot-com"); + assertThat(service.getCommand()).isEqualTo("bin/acme run"); + assertThat(service.getPorts()).containsExactly(8007, 8008); + } + + @Test + void builderWithImageAndTagUsesTag() { + DockerService service = DockerService.withImageAndTag("acme/toolbox:1.0").build(); + assertThat(service.getImage()).isEqualTo("acme/toolbox"); + assertThat(service.getImageTag()).isEqualTo("1.0"); + } + + @Test + void builderWithImageAndTagIsOverriddenByImageTag() { + DockerService service = DockerService.withImageAndTag("acme/toolbox:1.0").imageTag("2.0").build(); + assertThat(service.getImage()).isEqualTo("acme/toolbox"); + assertThat(service.getImageTag()).isEqualTo("2.0"); + } + + @Test + void builderWithCollectionOfPorts() { + DockerService service = DockerService.withImageAndTag("acme/toolbox").ports(List.of(8007, 8008)).build(); + assertThat(service.getPorts()).containsExactly(8007, 8008); + } + +} From df1b966cd39243d8ca16f0a2def8e8584db704e2 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Fri, 8 Sep 2023 14:57:44 -0500 Subject: [PATCH 3/3] Update Spring Pulsar for Boot 3.2.x * Split the Pulsar dependencies into Boot30/31 and Boot32 variants * Add DockerCompose and Testcontainers support * Enhance the tests for Pulsar --- .../SimpleDockerServiceResolver.java | 10 + ...gPulsarProjectGenerationConfiguration.java | 42 ++- start-site/src/main/resources/application.yml | 28 +- ...pringPulsarBinderBuildCustomizerTests.java | 62 ----- ...arProjectGenerationConfigurationTests.java | 249 ++++++++++++++++++ .../src/test/resources/compose/pulsar.yaml | 7 + 6 files changed, 322 insertions(+), 76 deletions(-) delete mode 100644 start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarBinderBuildCustomizerTests.java create mode 100644 start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfigurationTests.java create mode 100644 start-site/src/test/resources/compose/pulsar.yaml 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 7efbf0cdbb7..0be1c0f4341 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 @@ -42,6 +42,7 @@ public SimpleDockerServiceResolver() { this.dockerServices.put("mysql", mysql()); this.dockerServices.put("oracle", oracle()); this.dockerServices.put("postgres", postgres()); + this.dockerServices.put("pulsar", pulsar()); this.dockerServices.put("rabbit", rabbit()); this.dockerServices.put("redis", redis()); this.dockerServices.put("sqlServer", sqlServer()); @@ -103,6 +104,15 @@ private static DockerService postgres() { .build(); } + private static DockerService pulsar() { + // The latest tag they provide is not the 'latest' GA + return DockerService.withImageAndTag("apachepulsar/pulsar:3.1.0") + .website("https://hub.docker.com/r/apachepulsar/pulsar") + .command("bin/pulsar standalone") + .ports(8080, 6650) + .build(); + } + private static DockerService rabbit() { return DockerService.withImageAndTag("rabbitmq") .website("https://hub.docker.com/_/rabbitmq") diff --git a/start-site/src/main/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfiguration.java b/start-site/src/main/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfiguration.java index 38fc039d8b5..39a7ef91d7c 100644 --- a/start-site/src/main/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfiguration.java +++ b/start-site/src/main/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfiguration.java @@ -16,12 +16,21 @@ package io.spring.start.site.extension.dependency.springpulsar; +import io.spring.initializr.generator.condition.ConditionalOnPlatformVersion; import io.spring.initializr.generator.condition.ConditionalOnRequestedDependency; +import io.spring.initializr.generator.condition.ProjectGenerationCondition; 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; +import io.spring.start.site.container.ServiceConnectionsCustomizer; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.type.AnnotatedTypeMetadata; /** * Configuration for generation of projects that depend on Pulsar. @@ -29,14 +38,45 @@ * @author Chris Bono */ @ProjectGenerationConfiguration -@ConditionalOnRequestedDependency("pulsar") +@Conditional(SpringPulsarProjectGenerationConfiguration.OnPulsarRequestedDependencyCondition.class) class SpringPulsarProjectGenerationConfiguration { + private static final String TESTCONTAINERS_CLASS_NAME = "org.testcontainers.containers.PulsarContainer"; + + @Bean + @ConditionalOnPlatformVersion("3.2.0-SNAPSHOT") + @ConditionalOnRequestedDependency("testcontainers") + ServiceConnectionsCustomizer pulsarServiceConnectionsCustomizer(DockerServiceResolver serviceResolver) { + return (serviceConnections) -> serviceResolver.doWith("pulsar", + (service) -> serviceConnections.addServiceConnection(ServiceConnections.ServiceConnection + .ofContainer("pulsar", service, TESTCONTAINERS_CLASS_NAME, false))); + } + @Bean + @ConditionalOnPlatformVersion("3.2.0-SNAPSHOT") + @ConditionalOnRequestedDependency("docker-compose") + ComposeFileCustomizer pulsarComposeFileCustomizer(DockerServiceResolver serviceResolver) { + return (composeFile) -> serviceResolver.doWith("pulsar", + (service) -> composeFile.services().add("pulsar", service)); + } + + @Bean + @ConditionalOnPlatformVersion("[3.0.0,3.2.0-M1)") @ConditionalOnRequestedDependency("cloud-stream") SpringPulsarBinderBuildCustomizer pulsarBinderBuildCustomizer(InitializrMetadata metadata, ProjectDescription description) { return new SpringPulsarBinderBuildCustomizer(metadata, description); } + static class OnPulsarRequestedDependencyCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ProjectDescription description, ConditionContext context, + AnnotatedTypeMetadata metadata) { + return description.getRequestedDependencies().containsKey("pulsar") + || description.getRequestedDependencies().containsKey("pulsar-reactive"); + } + + } + } diff --git a/start-site/src/main/resources/application.yml b/start-site/src/main/resources/application.yml index 39529ab611c..1e05ce9581c 100644 --- a/start-site/src/main/resources/application.yml +++ b/start-site/src/main/resources/application.yml @@ -788,30 +788,32 @@ initializr: href: https://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/index.html#messaging.jms.artemis - name: Spring for Apache Pulsar id: pulsar - compatibilityRange: "[3.0.0,3.2.0-M1)" - mappings: - - compatibilityRange: "[3.0.0,3.2.0-M1)" - version: 0.2.0 + compatibilityRange: "[3.0.0,3.2.0-SNAPSHOT]" description: Build messaging applications with Apache Pulsar - groupId: org.springframework.pulsar - artifactId: spring-pulsar-spring-boot-starter links: - rel: reference - href: https://docs.spring.io/spring-pulsar/docs/0.2.x/reference/html/ - - name: Spring for Apache Pulsar (Reactive) - id: pulsar-reactive - compatibilityRange: "[3.0.0,3.2.0-M1)" + href: https://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/index.html#messaging.pulsar mappings: - compatibilityRange: "[3.0.0,3.2.0-M1)" version: 0.2.0 + groupId: org.springframework.pulsar + artifactId: spring-pulsar-spring-boot-starter + starter: false + - name: Spring for Apache Pulsar (Reactive) + id: pulsar-reactive + compatibilityRange: "[3.0.0,3.2.0-SNAPSHOT]" description: Build reactive messaging applications with Apache Pulsar facets: - reactive - groupId: org.springframework.pulsar - artifactId: spring-pulsar-reactive-spring-boot-starter links: - rel: reference - href: https://docs.spring.io/spring-pulsar/docs/0.2.x/reference/html/#reactive-pulsar + href: https://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/index.html#messaging.pulsar + mappings: + - compatibilityRange: "[3.0.0,3.2.0-M1)" + version: 0.2.0 + groupId: org.springframework.pulsar + artifactId: spring-pulsar-reactive-spring-boot-starter + starter: false - name: WebSocket id: websocket description: Build Servlet-based WebSocket applications with SockJS and STOMP. diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarBinderBuildCustomizerTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarBinderBuildCustomizerTests.java deleted file mode 100644 index 62c7b2ac408..00000000000 --- a/start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarBinderBuildCustomizerTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012-2023 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.springpulsar; - -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 static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link SpringPulsarBinderBuildCustomizer}. - * - * @author Chris Bono - */ -class SpringPulsarBinderBuildCustomizerTests extends AbstractExtensionTests { - - @Test - void binderNotAddedWhenPulsarNotSelected() { - ProjectStructure project = generateProject(createProjectRequest("cloud-stream")); - assertNoBinder(project); - } - - @Test - void binderNotAddedWhenCloudStreamNotSelected() { - ProjectRequest request = createProjectRequest("pulsar"); - request.setBootVersion("3.0.4"); - ProjectStructure project = generateProject(request); - assertNoBinder(project); - assertThat(project).mavenBuild().hasDependency(getDependency("pulsar")); - } - - @Test - void binderAddedWhenPulsarAndCloudStreamSelected() { - ProjectRequest request = createProjectRequest("pulsar", "cloud-stream"); - request.setBootVersion("3.0.4"); - ProjectStructure project = generateProject(request); - assertThat(project).mavenBuild() - .hasDependency("org.springframework.pulsar", "spring-pulsar-spring-cloud-stream-binder", "0.2.0"); - } - - private void assertNoBinder(ProjectStructure project) { - assertThat(project).mavenBuild() - .doesNotHaveDependency("org.springframework.pulsar", "spring-pulsar-spring-cloud-stream-binder"); - } - -} diff --git a/start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfigurationTests.java b/start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfigurationTests.java new file mode 100644 index 00000000000..d3d15139255 --- /dev/null +++ b/start-site/src/test/java/io/spring/start/site/extension/dependency/springpulsar/SpringPulsarProjectGenerationConfigurationTests.java @@ -0,0 +1,249 @@ +/* + * Copyright 2012-2023 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.springpulsar; + +import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.project.MutableProjectDescription; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.start.site.container.DockerServiceResolver; +import io.spring.start.site.container.ServiceConnections; +import io.spring.start.site.container.ServiceConnectionsCustomizer; +import io.spring.start.site.container.SimpleDockerServiceResolver; +import io.spring.start.site.extension.AbstractExtensionTests; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SpringPulsarProjectGenerationConfiguration}. + * + * @author Chris Bono + */ +class SpringPulsarProjectGenerationConfigurationTests extends AbstractExtensionTests { + + @Nested + class PulsarDependencyConfigurationTests { + + @ParameterizedTest + @ValueSource(strings = { "3.0.0", "3.1.3" }) + void pulsarLegacyStarterUsedWhenBoot30orBoot31Selected(String bootVersion) { + ProjectRequest request = createProjectRequest("pulsar"); + request.setBootVersion(bootVersion); + ProjectStructure project = generateProject(request); + assertThat(project).mavenBuild() + .hasDependency("org.springframework.pulsar", "spring-pulsar-spring-boot-starter"); + } + + @ParameterizedTest + @ValueSource(strings = { "3.0.0", "3.1.3" }) + void pulsarReactiveLegacyStarterUsedWhenBoot30orBoot31Selected(String bootVersion) { + ProjectRequest request = createProjectRequest("pulsar-reactive"); + request.setBootVersion(bootVersion); + ProjectStructure project = generateProject(request); + assertThat(project).mavenBuild() + .hasDependency("org.springframework.pulsar", "spring-pulsar-reactive-spring-boot-starter"); + } + + @Test + void pulsarBootStarterUsedWhenBoot32Selected() { + ProjectRequest request = createProjectRequest("pulsar"); + request.setBootVersion("3.2.0-SNAPSHOT"); + ProjectStructure project = generateProject(request); + assertThat(project).mavenBuild().hasDependency("org.springframework.boot", "spring-boot-starter-pulsar"); + } + + @Test + void pulsarReactiveBootStarterUsedWhenBoot32Selected() { + ProjectRequest request = createProjectRequest("pulsar-reactive"); + request.setBootVersion("3.2.0-SNAPSHOT"); + ProjectStructure project = generateProject(request); + assertThat(project).mavenBuild() + .hasDependency("org.springframework.boot", "spring-boot-starter-pulsar-reactive"); + } + + } + + @Nested + class DockerComposeConfigurationTests { + + @Test + void serviceNotCreatedWhenDockerComposeNotSelected() { + ProjectRequest request = createProjectRequest("pulsar"); + request.setBootVersion("3.2.0-SNAPSHOT"); + ProjectStructure structure = generateProject(request); + assertThat(structure.getProjectDirectory().resolve("compose.yaml")).doesNotExist(); + } + + @ParameterizedTest + @ValueSource(strings = { "3.1.3", "3.2.0-M2" }) + void serviceNotCreatedWhenIncompatibleBootVersionSelected(String bootVersion) { + ProjectRequest request = createProjectRequest("docker-compose", "pulsar"); + request.setBootVersion(bootVersion); + assertThat(composeFile(request)).doesNotContain("pulsar"); + } + + @ParameterizedTest + @ValueSource(strings = { "pulsar", "pulsar-reactive" }) + void serviceCreatedWhenDockerComposeSelectedWithCompatibleBootVersion(String pulsarDependencyId) { + ProjectRequest request = createProjectRequest("docker-compose", pulsarDependencyId); + request.setBootVersion("3.2.0-SNAPSHOT"); + assertThat(composeFile(request)).hasSameContentAs(new ClassPathResource("compose/pulsar.yaml")); + } + + } + + @Nested + class ServiceConnectionConfigurationTests { + + private final ProjectAssetTester projectTester = new ProjectAssetTester() + .withConfiguration(SpringPulsarProjectGenerationConfiguration.class) + .withBean(DockerServiceResolver.class, () -> new SimpleDockerServiceResolver()); + + @Test + void connectionNotAddedWhenTestcontainersNotSelected() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse("3.2.0-SNAPSHOT")); + description.addDependency("pulsar", mock(Dependency.class)); + this.projectTester.configure(description, + (context) -> assertThat(context).doesNotHaveBean("pulsarServiceConnectionsCustomizer")); + } + + @Test + void connectionNotAddedWhenPulsarNotSelected() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse("3.2.0-SNAPSHOT")); + description.addDependency("testcontainers", mock(Dependency.class)); + this.projectTester.configure(description, + (context) -> assertThat(context).doesNotHaveBean("pulsarServiceConnectionsCustomizer")); + } + + @ParameterizedTest + @ValueSource(strings = { "3.0.0", "3.1.3" }) + void connectionNotAddedWhenIncompatibleBootVersionSelected(String bootVersion) { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse(bootVersion)); + description.addDependency("pulsar", mock(Dependency.class)); + description.addDependency("testcontainers", mock(Dependency.class)); + this.projectTester.configure(description, + (context) -> assertThat(context).doesNotHaveBean("pulsarServiceConnectionsCustomizer")); + } + + @ParameterizedTest + @ValueSource(strings = { "pulsar", "pulsar-reactive" }) + void connectionAddedWhenTestcontainersAndPulsarSelectedWithCompatibleBootVersion(String pulsarDependencyId) { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse("3.2.0-SNAPSHOT")); + description.addDependency("testcontainers", mock(Dependency.class)); + description.addDependency(pulsarDependencyId, mock(Dependency.class)); + this.projectTester.configure(description, + (context) -> assertThat(context) + .getBean("pulsarServiceConnectionsCustomizer", ServiceConnectionsCustomizer.class) + .satisfies((customizer) -> { + ServiceConnections connections = new ServiceConnections(); + customizer.customize(connections); + assertPulsarServiceConnectionAddded(connections); + })); + } + + private void assertPulsarServiceConnectionAddded(ServiceConnections connections) { + assertThat(connections.values()).first().satisfies((connection) -> { + assertThat(connection.id()).isEqualTo("pulsar"); + assertThat(connection.containerClassName()).isEqualTo("org.testcontainers.containers.PulsarContainer"); + assertThat(connection.isGenericContainer()).isFalse(); + assertThat(connection.containerClassNameGeneric()).isFalse(); + assertThat(connection.dockerService()).satisfies((dockerService) -> { + assertThat(dockerService.getImage()).isEqualTo("apachepulsar/pulsar"); + assertThat(dockerService.getImageTag()).isEqualTo("3.1.0"); + assertThat(dockerService.getWebsite()).isEqualTo("https://hub.docker.com/r/apachepulsar/pulsar"); + assertThat(dockerService.getCommand()).isEqualTo("bin/pulsar standalone"); + assertThat(dockerService.getPorts()).containsExactlyInAnyOrder(8080, 6650); + }); + }); + } + + } + + @Nested + class SpringPulsarBinderConfigurationTests { + + @Test + void binderNotAddedWhenCloudStreamNotSelected() { + ProjectRequest request = createProjectRequest("pulsar"); + request.setBootVersion("3.1.3"); + ProjectStructure project = generateProject(request); + assertNoBinder(project); + assertThat(project).mavenBuild() + .hasDependency("org.springframework.pulsar", "spring-pulsar-spring-boot-starter"); + } + + @Test + void binderNotAddedWhenPulsarNotSelected() { + ProjectRequest request = createProjectRequest("cloud-stream"); + request.setBootVersion("3.1.3"); + ProjectStructure project = generateProject(request); + assertNoBinder(project); + } + + @ParameterizedTest + @ValueSource(strings = { "3.2.0-M1", "3.2.0-SNAPSHOT" }) + void binderNotAddedWhenPulsarAndCloudStreamSelectedWithIncompatibleBootVersion(String bootVersion) { + ProjectRequest request = createProjectRequest("pulsar", "cloud-stream"); + request.setBootVersion(bootVersion); + ProjectStructure project = generateProject(request); + assertNoBinder(project); + } + + @ParameterizedTest + @ValueSource(strings = { "3.0.0", "3.1.3", "3.1.4-SNAPSHOT" }) + void binderAddedWhenPulsarAndCloudStreamSelectedWithCompatibleBootVersion(String bootVersion) { + ProjectRequest request = createProjectRequest("pulsar", "cloud-stream"); + request.setBootVersion(bootVersion); + ProjectStructure project = generateProject(request); + assertBinder(project); + } + + @ParameterizedTest + @ValueSource(strings = { "3.0.0", "3.1.3", "3.1.4-SNAPSHOT" }) + void binderAddedWhenPulsarReactiveAndCloudStreamSelectedWithCompatibleBootVersion(String bootVersion) { + ProjectRequest request = createProjectRequest("pulsar-reactive", "cloud-stream"); + request.setBootVersion(bootVersion); + ProjectStructure project = generateProject(request); + assertBinder(project); + } + + private void assertNoBinder(ProjectStructure project) { + assertThat(project).mavenBuild() + .doesNotHaveDependency("org.springframework.pulsar", "spring-pulsar-spring-cloud-stream-binder"); + } + + private void assertBinder(ProjectStructure project) { + assertThat(project).mavenBuild() + .hasDependency("org.springframework.pulsar", "spring-pulsar-spring-cloud-stream-binder", "0.2.0"); + } + + } + +} diff --git a/start-site/src/test/resources/compose/pulsar.yaml b/start-site/src/test/resources/compose/pulsar.yaml new file mode 100644 index 00000000000..1bca13fe254 --- /dev/null +++ b/start-site/src/test/resources/compose/pulsar.yaml @@ -0,0 +1,7 @@ +services: + pulsar: + image: 'apachepulsar/pulsar:3.1.0' + ports: + - '6650' + - '8080' + command: 'bin/pulsar standalone'