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
This commit is contained in:
		
							parent
							
								
									2ab0e024c8
								
							
						
					
					
						commit
						6d192e62fd
					
				| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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.core.RunningService;
 | 
				
			||||||
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait;
 | 
					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;
 | 
				
			||||||
 | 
					import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start.Skip;
 | 
				
			||||||
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop;
 | 
					import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop;
 | 
				
			||||||
import org.springframework.context.ApplicationContext;
 | 
					import org.springframework.context.ApplicationContext;
 | 
				
			||||||
import org.springframework.context.ApplicationListener;
 | 
					import org.springframework.context.ApplicationListener;
 | 
				
			||||||
| 
						 | 
					@ -119,7 +120,11 @@ class DockerComposeLifecycleManager {
 | 
				
			||||||
		Wait wait = this.properties.getReadiness().getWait();
 | 
							Wait wait = this.properties.getReadiness().getWait();
 | 
				
			||||||
		List<RunningService> runningServices = dockerCompose.getRunningServices();
 | 
							List<RunningService> runningServices = dockerCompose.getRunningServices();
 | 
				
			||||||
		if (lifecycleManagement.shouldStart()) {
 | 
							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());
 | 
									start.getCommand().applyTo(dockerCompose, start.getLogLevel());
 | 
				
			||||||
				runningServices = dockerCompose.getRunningServices();
 | 
									runningServices = dockerCompose.getRunningServices();
 | 
				
			||||||
				if (wait == Wait.ONLY_IF_STARTED) {
 | 
									if (wait == Wait.ONLY_IF_STARTED) {
 | 
				
			||||||
| 
						 | 
					@ -129,9 +134,6 @@ class DockerComposeLifecycleManager {
 | 
				
			||||||
					this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
 | 
										this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			else {
 | 
					 | 
				
			||||||
				logger.info("There are already Docker Compose services running, skipping startup");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		List<RunningService> relevantServices = new ArrayList<>(runningServices);
 | 
							List<RunningService> relevantServices = new ArrayList<>(runningServices);
 | 
				
			||||||
		relevantServices.removeIf(this::isIgnored);
 | 
							relevantServices.removeIf(this::isIgnored);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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.io.File;
 | 
				
			||||||
import java.time.Duration;
 | 
					import java.time.Duration;
 | 
				
			||||||
import java.util.LinkedHashSet;
 | 
					import java.util.LinkedHashSet;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
					import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
				
			||||||
import org.springframework.boot.context.properties.bind.Binder;
 | 
					import org.springframework.boot.context.properties.bind.Binder;
 | 
				
			||||||
 | 
					import org.springframework.boot.docker.compose.core.RunningService;
 | 
				
			||||||
import org.springframework.boot.logging.LogLevel;
 | 
					import org.springframework.boot.logging.LogLevel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -148,6 +150,11 @@ public class DockerComposeProperties {
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		private LogLevel logLevel = LogLevel.INFO;
 | 
							private LogLevel logLevel = LogLevel.INFO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Whether to skip executing the start command.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							private Skip skip = Skip.IF_RUNNING;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public StartCommand getCommand() {
 | 
							public StartCommand getCommand() {
 | 
				
			||||||
			return this.command;
 | 
								return this.command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -164,6 +171,51 @@ public class DockerComposeProperties {
 | 
				
			||||||
			this.logLevel = logLevel;
 | 
								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<RunningService> runningServices) {
 | 
				
			||||||
 | 
										return false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								/**
 | 
				
			||||||
 | 
								 * Skip start if there are already services running.
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								IF_RUNNING {
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									boolean shouldSkip(List<RunningService> runningServices) {
 | 
				
			||||||
 | 
										return !runningServices.isEmpty();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									String getLogMessage() {
 | 
				
			||||||
 | 
										return "There are already Docker Compose services running, skipping startup";
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								abstract boolean shouldSkip(List<RunningService> runningServices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								String getLogMessage() {
 | 
				
			||||||
 | 
									return "";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,10 @@
 | 
				
			||||||
      "name": "spring.docker.compose.start.log-level",
 | 
					      "name": "spring.docker.compose.start.log-level",
 | 
				
			||||||
      "defaultValue": "info"
 | 
					      "defaultValue": "info"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "spring.docker.compose.start.skip",
 | 
				
			||||||
 | 
					      "defaultValue": "if-running"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "name": "spring.docker.compose.stop.command",
 | 
					      "name": "spring.docker.compose.stop.command",
 | 
				
			||||||
      "defaultValue": "stop"
 | 
					      "defaultValue": "stop"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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.DockerComposeFile;
 | 
				
			||||||
import org.springframework.boot.docker.compose.core.RunningService;
 | 
					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.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.CapturedOutput;
 | 
				
			||||||
import org.springframework.boot.test.system.OutputCaptureExtension;
 | 
					import org.springframework.boot.test.system.OutputCaptureExtension;
 | 
				
			||||||
import org.springframework.context.ApplicationContext;
 | 
					import org.springframework.context.ApplicationContext;
 | 
				
			||||||
| 
						 | 
					@ -384,6 +385,38 @@ class DockerComposeLifecycleManagerTests {
 | 
				
			||||||
		assertThat(output).doesNotContain("There are already Docker Compose services running, skipping startup");
 | 
							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() {
 | 
						private void setUpRunningServices() {
 | 
				
			||||||
		setUpRunningServices(true);
 | 
							setUpRunningServices(true);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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.io.File;
 | 
				
			||||||
import java.time.Duration;
 | 
					import java.time.Duration;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.LinkedHashMap;
 | 
					import java.util.LinkedHashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.context.properties.bind.Binder;
 | 
					import org.springframework.boot.context.properties.bind.Binder;
 | 
				
			||||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
 | 
					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.Readiness.Wait;
 | 
				
			||||||
 | 
					import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start.Skip;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Tests for {@link DockerComposeProperties}.
 | 
					 * Tests for {@link DockerComposeProperties}.
 | 
				
			||||||
| 
						 | 
					@ -84,4 +89,16 @@ class DockerComposePropertiesTests {
 | 
				
			||||||
		assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(500));
 | 
							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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue