From 6d192e62fd5bb8a243e6458d859b1194fcf208aa Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 5 Apr 2024 08:36:21 +0200 Subject: [PATCH] Add property to control Docker Compose start command execution If the property 'spring.docker.compose.start.skip' is set to 'never', the start command is always executed. The default value of 'if-running' only executes the start command if there are no services running already, which is the old behavior. Closes gh-39749 --- .../DockerComposeLifecycleManager.java | 12 +++-- .../lifecycle/DockerComposeProperties.java | 54 ++++++++++++++++++- ...itional-spring-configuration-metadata.json | 4 ++ .../DockerComposeLifecycleManagerTests.java | 35 +++++++++++- .../DockerComposePropertiesTests.java | 19 ++++++- 5 files changed, 116 insertions(+), 8 deletions(-) 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 7499cf19433..7b7a0952248 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -32,6 +32,7 @@ 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.Start.Skip; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -119,7 +120,11 @@ class DockerComposeLifecycleManager { Wait wait = this.properties.getReadiness().getWait(); List runningServices = dockerCompose.getRunningServices(); if (lifecycleManagement.shouldStart()) { - if (runningServices.isEmpty()) { + Skip skip = this.properties.getStart().getSkip(); + if (skip.shouldSkip(runningServices)) { + logger.info(skip.getLogMessage()); + } + else { start.getCommand().applyTo(dockerCompose, start.getLogLevel()); runningServices = dockerCompose.getRunningServices(); if (wait == Wait.ONLY_IF_STARTED) { @@ -129,9 +134,6 @@ class DockerComposeLifecycleManager { this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout())); } } - else { - logger.info("There are already Docker Compose services running, skipping startup"); - } } List relevantServices = new ArrayList<>(runningServices); relevantServices.removeIf(this::isIgnored); 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 a25f9f7a2a8..df56f712379 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -19,10 +19,12 @@ package org.springframework.boot.docker.compose.lifecycle; import java.io.File; import java.time.Duration; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.logging.LogLevel; /** @@ -148,6 +150,11 @@ public class DockerComposeProperties { */ private LogLevel logLevel = LogLevel.INFO; + /** + * Whether to skip executing the start command. + */ + private Skip skip = Skip.IF_RUNNING; + public StartCommand getCommand() { return this.command; } @@ -164,6 +171,51 @@ public class DockerComposeProperties { this.logLevel = logLevel; } + public Skip getSkip() { + return this.skip; + } + + public void setSkip(Skip skip) { + this.skip = skip; + } + + /** + * Start command skip mode. + */ + public enum Skip { + + /** + * Never skip start. + */ + NEVER { + @Override + boolean shouldSkip(List runningServices) { + return false; + } + }, + /** + * Skip start if there are already services running. + */ + IF_RUNNING { + @Override + boolean shouldSkip(List runningServices) { + return !runningServices.isEmpty(); + } + + @Override + String getLogMessage() { + return "There are already Docker Compose services running, skipping startup"; + } + }; + + abstract boolean shouldSkip(List runningServices); + + String getLogMessage() { + return ""; + } + + } + } /** diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 4ec0d8d247b..3d2b4e37383 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -18,6 +18,10 @@ "name": "spring.docker.compose.start.log-level", "defaultValue": "info" }, + { + "name": "spring.docker.compose.start.skip", + "defaultValue": "if-running" + }, { "name": "spring.docker.compose.stop.command", "defaultValue": "stop" 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 118841abf92..6d4cad8d69e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -39,6 +39,7 @@ 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.Skip; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.ApplicationContext; @@ -384,6 +385,38 @@ class DockerComposeLifecycleManagerTests { assertThat(output).doesNotContain("There are already Docker Compose services running, skipping startup"); } + @Test + void shouldStartIfSkipModeIsIfRunningAndNoServicesAreRunning() { + given(this.dockerCompose.hasDefinedServices()).willReturn(true); + this.properties.getStart().setSkip(Skip.IF_RUNNING); + this.lifecycleManager.start(); + then(this.dockerCompose).should().up(any()); + } + + @Test + void shouldNotStartIfSkipModeIsIfRunningAndServicesAreAlreadyRunning() { + setUpRunningServices(); + this.properties.getStart().setSkip(Skip.IF_RUNNING); + this.lifecycleManager.start(); + then(this.dockerCompose).should(never()).up(any()); + } + + @Test + void shouldStartIfSkipModeIsNeverAndNoServicesAreRunning() { + given(this.dockerCompose.hasDefinedServices()).willReturn(true); + this.properties.getStart().setSkip(Skip.NEVER); + this.lifecycleManager.start(); + then(this.dockerCompose).should().up(any()); + } + + @Test + void shouldStartIfSkipModeIsNeverAndServicesAreAlreadyRunning() { + setUpRunningServices(); + this.properties.getStart().setSkip(Skip.NEVER); + this.lifecycleManager.start(); + then(this.dockerCompose).should().up(any()); + } + private void setUpRunningServices() { setUpRunningServices(true); } 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 6344d810b82..b61f26a33dc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -18,16 +18,21 @@ package org.springframework.boot.docker.compose.lifecycle; import java.io.File; import java.time.Duration; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; 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.core.RunningService; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start.Skip; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link DockerComposeProperties}. @@ -84,4 +89,16 @@ class DockerComposePropertiesTests { assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(500)); } + @Test + void skipModeNeverShouldNeverSkip() { + assertThat(Skip.NEVER.shouldSkip(Collections.emptyList())).isFalse(); + assertThat(Skip.NEVER.shouldSkip(List.of(mock(RunningService.class)))).isFalse(); + } + + @Test + void skipModeIfRunningShouldSkipWhenServicesAreRunning() { + assertThat(Skip.IF_RUNNING.shouldSkip(Collections.emptyList())).isFalse(); + assertThat(Skip.IF_RUNNING.shouldSkip(List.of(mock(RunningService.class)))).isTrue(); + } + }