diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java index 72498b045c..e04ad34e4f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java @@ -29,6 +29,7 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.docker.compose.core.DockerCompose; import org.springframework.boot.docker.compose.core.DockerComposeFile; import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop; import org.springframework.context.ApplicationContext; @@ -110,15 +111,19 @@ class DockerComposeLifecycleManager { LifecycleManagement lifecycleManagement = this.properties.getLifecycleManagement(); Start start = this.properties.getStart(); Stop stop = this.properties.getStop(); + Wait wait = this.properties.getReadiness().getWait(); if (lifecycleManagement.shouldStart() && !dockerCompose.hasRunningServices()) { start.getCommand().applyTo(dockerCompose, start.getLogLevel()); + wait = (wait != Wait.ONLY_IF_STARTED) ? wait : Wait.ALWAYS; if (lifecycleManagement.shouldStop()) { this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout())); } } List runningServices = new ArrayList<>(dockerCompose.getRunningServices()); runningServices.removeIf(this::isIgnored); - this.serviceReadinessChecks.waitUntilReady(runningServices); + if (wait == Wait.ALWAYS || wait == null) { + this.serviceReadinessChecks.waitUntilReady(runningServices); + } publishEvent(new DockerComposeServicesReadyEvent(this.applicationContext, runningServices)); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java index 26cea3ff04..569f79c135 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java @@ -244,6 +244,11 @@ public class DockerComposeProperties { */ public static class Readiness { + /** + * Wait strategy to use. + */ + private Wait wait = Wait.ALWAYS; + /** * Timeout of the readiness checks. */ @@ -254,6 +259,14 @@ public class DockerComposeProperties { */ private final Tcp tcp = new Tcp(); + public Wait getWait() { + return this.wait; + } + + public void setWait(Wait wait) { + this.wait = wait; + } + public Duration getTimeout() { return this.timeout; } @@ -266,6 +279,29 @@ public class DockerComposeProperties { return this.tcp; } + /** + * Readiness wait strategies. + */ + public enum Wait { + + /** + * Always perform readiness checks. + */ + ALWAYS, + + /** + * Always perform readiness checks. + */ + NEVER, + + /** + * Only perform readiness checks if docker was started with lifecycle + * management. + */ + ONLY_IF_STARTED + + } + /** * TCP properties. */ diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java index 89d67a19bd..943e172e67 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java @@ -36,6 +36,7 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.docker.compose.core.DockerCompose; import org.springframework.boot.docker.compose.core.DockerComposeFile; import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.support.GenericApplicationContext; @@ -268,7 +269,7 @@ class DockerComposeLifecycleManagerTests { void startWhenHasIgnoreLabelIgnoresService() { EventCapturingListener listener = new EventCapturingListener(); this.eventListeners.add(listener); - setUpRunningServices(Map.of("org.springframework.boot.ignore", "true")); + setUpRunningServices(true, Map.of("org.springframework.boot.ignore", "true")); this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); @@ -285,6 +286,40 @@ class DockerComposeLifecycleManagerTests { then(this.serviceReadinessChecks).should().waitUntilReady(this.runningServices); } + @Test + void startWhenWaitNeverDoesNotWaitUntilReady() { + this.properties.getReadiness().setWait(Wait.NEVER); + EventCapturingListener listener = new EventCapturingListener(); + this.eventListeners.add(listener); + setUpRunningServices(); + this.lifecycleManager.start(); + this.shutdownHandlers.run(); + then(this.serviceReadinessChecks).should(never()).waitUntilReady(this.runningServices); + } + + @Test + void startWhenWaitOnlyIfStartedAndNotStartedDoesNotWaitUntilReady() { + this.properties.getReadiness().setWait(Wait.ONLY_IF_STARTED); + this.properties.setLifecycleManagement(LifecycleManagement.NONE); + EventCapturingListener listener = new EventCapturingListener(); + this.eventListeners.add(listener); + setUpRunningServices(); + this.lifecycleManager.start(); + this.shutdownHandlers.run(); + then(this.serviceReadinessChecks).should(never()).waitUntilReady(this.runningServices); + } + + @Test + void startWhenWaitOnlyIfStartedAndStartedWaitsUntilReady() { + this.properties.getReadiness().setWait(Wait.ONLY_IF_STARTED); + EventCapturingListener listener = new EventCapturingListener(); + this.eventListeners.add(listener); + setUpRunningServices(false); + this.lifecycleManager.start(); + this.shutdownHandlers.run(); + then(this.serviceReadinessChecks).should().waitUntilReady(this.runningServices); + } + @Test void startGetsDockerComposeWithActiveProfiles() { this.properties.getProfiles().setActive(Set.of("my-profile")); @@ -306,16 +341,26 @@ class DockerComposeLifecycleManagerTests { } private void setUpRunningServices() { - setUpRunningServices(Collections.emptyMap()); + setUpRunningServices(true); } - private void setUpRunningServices(Map labels) { + private void setUpRunningServices(boolean started) { + setUpRunningServices(started, Collections.emptyMap()); + } + + @SuppressWarnings("unchecked") + private void setUpRunningServices(boolean started, Map labels) { given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasRunningServices()).willReturn(true); RunningService runningService = mock(RunningService.class); given(runningService.labels()).willReturn(labels); this.runningServices = List.of(runningService); - given(this.dockerCompose.getRunningServices()).willReturn(this.runningServices); + if (started) { + given(this.dockerCompose.getRunningServices()).willReturn(this.runningServices); + } + else { + given(this.dockerCompose.getRunningServices()).willReturn(Collections.emptyList(), this.runningServices); + } } /** diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java index 03103c73fb..6344d810b8 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; import static org.assertj.core.api.Assertions.assertThat; @@ -48,6 +49,7 @@ class DockerComposePropertiesTests { assertThat(properties.getStop().getCommand()).isEqualTo(StopCommand.STOP); assertThat(properties.getStop().getTimeout()).isEqualTo(Duration.ofSeconds(10)); assertThat(properties.getProfiles().getActive()).isEmpty(); + assertThat(properties.getReadiness().getWait()).isEqualTo(Wait.ALWAYS); assertThat(properties.getReadiness().getTimeout()).isEqualTo(Duration.ofMinutes(2)); assertThat(properties.getReadiness().getTcp().getConnectTimeout()).isEqualTo(Duration.ofMillis(200)); assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(200)); @@ -63,6 +65,7 @@ class DockerComposePropertiesTests { source.put("spring.docker.compose.stop.command", "down"); source.put("spring.docker.compose.stop.timeout", "5s"); source.put("spring.docker.compose.profiles.active", "myprofile"); + source.put("spring.docker.compose.readiness.wait", "only-if-started"); source.put("spring.docker.compose.readiness.timeout", "10s"); source.put("spring.docker.compose.readiness.tcp.connect-timeout", "400ms"); source.put("spring.docker.compose.readiness.tcp.read-timeout", "500ms"); @@ -75,6 +78,7 @@ class DockerComposePropertiesTests { assertThat(properties.getStop().getCommand()).isEqualTo(StopCommand.DOWN); assertThat(properties.getStop().getTimeout()).isEqualTo(Duration.ofSeconds(5)); assertThat(properties.getProfiles().getActive()).containsExactly("myprofile"); + assertThat(properties.getReadiness().getWait()).isEqualTo(Wait.ONLY_IF_STARTED); assertThat(properties.getReadiness().getTimeout()).isEqualTo(Duration.ofSeconds(10)); assertThat(properties.getReadiness().getTcp().getConnectTimeout()).isEqualTo(Duration.ofMillis(400)); assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(500));