Change DockerComposeProperties shut down default to stop

Closes gh-35239
pull/35333/head
Andy Wilkinson 2 years ago
parent 6a39b497ad
commit 4f9616c2f9

@ -33,35 +33,35 @@ import org.springframework.boot.logging.LogLevel;
public interface DockerCompose { public interface DockerCompose {
/** /**
* Timeout duration used to request a forced shutdown. * Timeout duration used to request a forced stop.
*/ */
Duration FORCE_SHUTDOWN = Duration.ZERO; Duration FORCE_STOP = Duration.ZERO;
/** /**
* Run {@code docker compose up} to startup services. Waits until all contains are * Run {@code docker compose up} to create and start services. Waits until all
* started and healthy. * contains are started and healthy.
* @param logLevel the log level used to report progress * @param logLevel the log level used to report progress
*/ */
void up(LogLevel logLevel); void up(LogLevel logLevel);
/** /**
* Run {@code docker compose down} to shut down any running services. * Run {@code docker compose down} to stop and remove any running services.
* @param timeout the amount of time to wait or {@link #FORCE_SHUTDOWN} to shut down * @param timeout the amount of time to wait or {@link #FORCE_STOP} to stop without
* without waiting. * waiting.
*/ */
void down(Duration timeout); void down(Duration timeout);
/** /**
* Run {@code docker compose start} to startup services. Waits until all contains are * Run {@code docker compose start} to start services. Waits until all containers are
* started and healthy. * started and healthy.
* @param logLevel the log level used to report progress * @param logLevel the log level used to report progress
*/ */
void start(LogLevel logLevel); void start(LogLevel logLevel);
/** /**
* Run {@code docker compose stop} to shut down any running services. * Run {@code docker compose stop} to stop any running services.
* @param timeout the amount of time to wait or {@link #FORCE_SHUTDOWN} to shut down * @param timeout the amount of time to wait or {@link #FORCE_STOP} to stop without
* without waiting. * waiting.
*/ */
void stop(Duration timeout); void stop(Duration timeout);

@ -29,8 +29,8 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.docker.compose.core.DockerCompose; 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.Shutdown; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start;
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Startup; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop;
import org.springframework.boot.docker.compose.readiness.ServiceReadinessChecks; import org.springframework.boot.docker.compose.readiness.ServiceReadinessChecks;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
@ -89,30 +89,30 @@ class DockerComposeLifecycleManager {
: new ServiceReadinessChecks(this.classLoader, applicationContext.getEnvironment(), binder); : new ServiceReadinessChecks(this.classLoader, applicationContext.getEnvironment(), binder);
} }
void startup() { void start() {
if (!this.properties.isEnabled()) { if (!this.properties.isEnabled()) {
logger.trace("Docker compose support not enabled"); logger.trace("Docker Compose support not enabled");
return; return;
} }
if (this.skipCheck.shouldSkip(this.classLoader, this.properties.getSkip())) { if (this.skipCheck.shouldSkip(this.classLoader, this.properties.getSkip())) {
logger.trace("Docker compose support skipped"); logger.trace("Docker Compose support skipped");
return; return;
} }
DockerComposeFile composeFile = getComposeFile(); DockerComposeFile composeFile = getComposeFile();
Set<String> activeProfiles = this.properties.getProfiles().getActive(); Set<String> activeProfiles = this.properties.getProfiles().getActive();
DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles); DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles);
if (!dockerCompose.hasDefinedServices()) { if (!dockerCompose.hasDefinedServices()) {
logger.warn(LogMessage.format("No services defined in docker compose file '%s' with active profiles %s", logger.warn(LogMessage.format("No services defined in Docker Compose file '%s' with active profiles %s",
composeFile, activeProfiles)); composeFile, activeProfiles));
return; return;
} }
LifecycleManagement lifecycleManagement = this.properties.getLifecycleManagement(); LifecycleManagement lifecycleManagement = this.properties.getLifecycleManagement();
Startup startup = this.properties.getStartup(); Start start = this.properties.getStart();
Shutdown shutdown = this.properties.getShutdown(); Stop stop = this.properties.getStop();
if (lifecycleManagement.shouldStartup() && !dockerCompose.hasRunningServices()) { if (lifecycleManagement.shouldStart() && !dockerCompose.hasRunningServices()) {
startup.getCommand().applyTo(dockerCompose, startup.getLogLevel()); start.getCommand().applyTo(dockerCompose, start.getLogLevel());
if (lifecycleManagement.shouldShutdown()) { if (lifecycleManagement.shouldStop()) {
this.shutdownHandlers.add(() -> shutdown.getCommand().applyTo(dockerCompose, shutdown.getTimeout())); this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
} }
} }
List<RunningService> runningServices = new ArrayList<>(dockerCompose.getRunningServices()); List<RunningService> runningServices = new ArrayList<>(dockerCompose.getRunningServices());
@ -124,7 +124,7 @@ class DockerComposeLifecycleManager {
protected DockerComposeFile getComposeFile() { protected DockerComposeFile getComposeFile() {
DockerComposeFile composeFile = (this.properties.getFile() != null) DockerComposeFile composeFile = (this.properties.getFile() != null)
? DockerComposeFile.of(this.properties.getFile()) : DockerComposeFile.find(this.workingDirectory); ? DockerComposeFile.of(this.properties.getFile()) : DockerComposeFile.find(this.workingDirectory);
logger.info(LogMessage.format("Found docker compose file '%s'", composeFile)); logger.info(LogMessage.format("Found Docker Compose file '%s'", composeFile));
return composeFile; return composeFile;
} }

@ -50,7 +50,7 @@ class DockerComposeListener implements ApplicationListener<ApplicationPreparedEv
Binder binder = Binder.get(applicationContext.getEnvironment()); Binder binder = Binder.get(applicationContext.getEnvironment());
DockerComposeProperties properties = DockerComposeProperties.get(binder); DockerComposeProperties properties = DockerComposeProperties.get(binder);
Set<ApplicationListener<?>> eventListeners = event.getSpringApplication().getListeners(); Set<ApplicationListener<?>> eventListeners = event.getSpringApplication().getListeners();
createDockerComposeLifecycleManager(applicationContext, binder, properties, eventListeners).startup(); createDockerComposeLifecycleManager(applicationContext, binder, properties, eventListeners).start();
} }
protected DockerComposeLifecycleManager createDockerComposeLifecycleManager( protected DockerComposeLifecycleManager createDockerComposeLifecycleManager(

@ -26,7 +26,7 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LogLevel;
/** /**
* Configuration properties for the 'docker compose'. * Configuration properties for Docker Compose.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson
@ -61,12 +61,12 @@ public class DockerComposeProperties {
/** /**
* Start configuration. * Start configuration.
*/ */
private final Startup startup = new Startup(); private final Start start = new Start();
/** /**
* Stop configuration. * Stop configuration.
*/ */
private final Shutdown shutdown = new Shutdown(); private final Stop stop = new Stop();
/** /**
* Profiles configuration. * Profiles configuration.
@ -107,12 +107,12 @@ public class DockerComposeProperties {
this.host = host; this.host = host;
} }
public Startup getStartup() { public Start getStart() {
return this.startup; return this.start;
} }
public Shutdown getShutdown() { public Stop getStop() {
return this.shutdown; return this.stop;
} }
public Profiles getProfiles() { public Profiles getProfiles() {
@ -128,25 +128,25 @@ public class DockerComposeProperties {
} }
/** /**
* Startup properties. * Start properties.
*/ */
public static class Startup { public static class Start {
/** /**
* Command used to start docker compose. * Command used to start docker compose.
*/ */
private StartupCommand command = StartupCommand.UP; private StartCommand command = StartCommand.UP;
/** /**
* Log level for output. * Log level for output.
*/ */
private LogLevel logLevel = LogLevel.INFO; private LogLevel logLevel = LogLevel.INFO;
public StartupCommand getCommand() { public StartCommand getCommand() {
return this.command; return this.command;
} }
public void setCommand(StartupCommand command) { public void setCommand(StartCommand command) {
this.command = command; this.command = command;
} }
@ -161,25 +161,25 @@ public class DockerComposeProperties {
} }
/** /**
* Shutdown properties. * Stop properties.
*/ */
public static class Shutdown { public static class Stop {
/** /**
* Command used to stop docker compose. * Command used to stop docker compose.
*/ */
private ShutdownCommand command = ShutdownCommand.DOWN; private StopCommand command = StopCommand.STOP;
/** /**
* Timeout for stopping docker compose. Use '0' for forced stop. * Timeout for stopping Docker Compose. Use '0' for forced stop.
*/ */
private Duration timeout = Duration.ofSeconds(10); private Duration timeout = Duration.ofSeconds(10);
public ShutdownCommand getCommand() { public StopCommand getCommand() {
return this.command; return this.command;
} }
public void setCommand(ShutdownCommand command) { public void setCommand(StopCommand command) {
this.command = command; this.command = command;
} }

@ -24,9 +24,9 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
/** /**
* {@link ApplicationEvent} published when docker compose {@link RunningService} instances * {@link ApplicationEvent} published when Docker Compose {@link RunningService} instances
* are available. This event is published from the {@link ApplicationPreparedEvent} that * are available. This event is published from the {@link ApplicationPreparedEvent}
* performs the docker compose startup. * listener that starts Docker Compose.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson

@ -24,7 +24,7 @@ import org.springframework.boot.SpringApplicationAotProcessor;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
* Checks if docker compose support should be skipped. * Checks if Docker Compose support should be skipped.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */

@ -17,7 +17,7 @@
package org.springframework.boot.docker.compose.lifecycle; package org.springframework.boot.docker.compose.lifecycle;
/** /**
* Docker compose lifecycle management. * Docker Compose lifecycle management.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson
@ -27,43 +27,43 @@ package org.springframework.boot.docker.compose.lifecycle;
public enum LifecycleManagement { public enum LifecycleManagement {
/** /**
* Don't start or stop docker compose. * Don't start or stop Docker Compose.
*/ */
NONE(false, false), NONE(false, false),
/** /**
* Only start docker compose if it's not running. * Start Docker Compose if it's not running.
*/ */
START_ONLY(true, false), START_ONLY(true, false),
/** /**
* Start and stop docker compose if it's not running. * Start Docker Compose if it's not running and stop it when the JVM exits.
*/ */
START_AND_STOP(true, true); START_AND_STOP(true, true);
private final boolean startup; private final boolean start;
private final boolean shutdown; private final boolean stop;
LifecycleManagement(boolean startup, boolean shutdown) { LifecycleManagement(boolean start, boolean stop) {
this.startup = startup; this.start = start;
this.shutdown = shutdown; this.stop = stop;
} }
/** /**
* Return whether docker compose should be started. * Return whether Docker Compose should be started.
* @return whether docker compose should be started * @return whether Docker Compose should be started
*/ */
boolean shouldStartup() { boolean shouldStart() {
return this.startup; return this.start;
} }
/** /**
* Return whether docker compose should be stopped. * Return whether Docker Compose should be stopped.
* @return whether docker compose should be stopped * @return whether Docker Compose should be stopped
*/ */
boolean shouldShutdown() { boolean shouldStop() {
return this.shutdown; return this.stop;
} }
} }

@ -22,28 +22,28 @@ import org.springframework.boot.docker.compose.core.DockerCompose;
import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LogLevel;
/** /**
* Command used to startup docker compose. * Command used to start Docker Compose.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
* @since 3.1.0 * @since 3.1.0
*/ */
public enum StartupCommand { public enum StartCommand {
/** /**
* Startup using {@code docker compose up}. * Start using {@code docker compose up}.
*/ */
UP(DockerCompose::up), UP(DockerCompose::up),
/** /**
* Startup using {@code docker compose start}. * Start using {@code docker compose start}.
*/ */
START(DockerCompose::start); START(DockerCompose::start);
private final BiConsumer<DockerCompose, LogLevel> action; private final BiConsumer<DockerCompose, LogLevel> action;
StartupCommand(BiConsumer<DockerCompose, LogLevel> action) { StartCommand(BiConsumer<DockerCompose, LogLevel> action) {
this.action = action; this.action = action;
} }

@ -22,28 +22,28 @@ import java.util.function.BiConsumer;
import org.springframework.boot.docker.compose.core.DockerCompose; import org.springframework.boot.docker.compose.core.DockerCompose;
/** /**
* Command used to shut down docker compose. * Command used to stop Docker Compose.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
* @since 3.1.0 * @since 3.1.0
*/ */
public enum ShutdownCommand { public enum StopCommand {
/** /**
* Shutdown using {@code docker compose down}. * Stop using {@code docker compose down}.
*/ */
DOWN(DockerCompose::down), DOWN(DockerCompose::down),
/** /**
* Shutdown using {@code docker compose stop}. * Stop using {@code docker compose stop}.
*/ */
STOP(DockerCompose::stop); STOP(DockerCompose::stop);
private final BiConsumer<DockerCompose, Duration> action; private final BiConsumer<DockerCompose, Duration> action;
ShutdownCommand(BiConsumer<DockerCompose, Duration> action) { StopCommand(BiConsumer<DockerCompose, Duration> action) {
this.action = action; this.action = action;
} }

@ -15,6 +15,6 @@
*/ */
/** /**
* Lifecycle management for docker compose with the context of a Spring application. * Lifecycle management for Docker Compose with the context of a Spring application.
*/ */
package org.springframework.boot.docker.compose.lifecycle; package org.springframework.boot.docker.compose.lifecycle;

@ -103,32 +103,32 @@ class DockerComposeLifecycleManagerTests {
} }
@Test @Test
void startupWhenEnabledFalseDoesNotStart() { void startWhenEnabledFalseDoesNotStart() {
this.properties.setEnabled(false); this.properties.setEnabled(false);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
assertThat(listener.getEvent()).isNull(); assertThat(listener.getEvent()).isNull();
then(this.dockerCompose).should(never()).hasDefinedServices(); then(this.dockerCompose).should(never()).hasDefinedServices();
} }
@Test @Test
void startupWhenInTestDoesNotStart() { void startWhenInTestDoesNotStart() {
given(this.skipCheck.shouldSkip(any(), any())).willReturn(true); given(this.skipCheck.shouldSkip(any(), any())).willReturn(true);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
assertThat(listener.getEvent()).isNull(); assertThat(listener.getEvent()).isNull();
then(this.dockerCompose).should(never()).hasDefinedServices(); then(this.dockerCompose).should(never()).hasDefinedServices();
} }
@Test @Test
void startupWhenHasNoDefinedServicesDoesNothing() { void startWhenHasNoDefinedServicesDoesNothing() {
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
this.lifecycleManager.startup(); this.lifecycleManager.start();
assertThat(listener.getEvent()).isNull(); assertThat(listener.getEvent()).isNull();
then(this.dockerCompose).should().hasDefinedServices(); then(this.dockerCompose).should().hasDefinedServices();
then(this.dockerCompose).should(never()).up(any()); then(this.dockerCompose).should(never()).up(any());
@ -138,27 +138,27 @@ class DockerComposeLifecycleManagerTests {
} }
@Test @Test
void startupWhenLifecycleStartAndStopAndHasNoRunningServicesDoesStartupAndShutdown() { void startWhenLifecycleStartAndStopAndHasNoRunningServicesDoesUpAndStop() {
this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP); this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasDefinedServices()).willReturn(true);
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should().up(any()); then(this.dockerCompose).should().up(any());
then(this.dockerCompose).should(never()).start(any()); then(this.dockerCompose).should(never()).start(any());
then(this.dockerCompose).should().down(any()); then(this.dockerCompose).should().stop(any());
then(this.dockerCompose).should(never()).stop(any()); then(this.dockerCompose).should(never()).down(any());
} }
@Test @Test
void startupWhenLifecycleStartAndStopAndHasRunningServicesDoesNoStartupOrShutdown() { void startWhenLifecycleStartAndStopAndHasRunningServicesDoesNothing() {
this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP); this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should(never()).up(any()); then(this.dockerCompose).should(never()).up(any());
@ -168,12 +168,12 @@ class DockerComposeLifecycleManagerTests {
} }
@Test @Test
void startupWhenLifecycleNoneDoesNoStartupOrShutdown() { void startWhenLifecycleNoneDoesNothing() {
this.properties.setLifecycleManagement(LifecycleManagement.NONE); this.properties.setLifecycleManagement(LifecycleManagement.NONE);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should(never()).up(any()); then(this.dockerCompose).should(never()).up(any());
@ -183,12 +183,12 @@ class DockerComposeLifecycleManagerTests {
} }
@Test @Test
void startupWhenLifecycleStartOnlyDoesStartupAndNoShutdown() { void startWhenLifecycleStartOnlyDoesOnlyStart() {
this.properties.setLifecycleManagement(LifecycleManagement.START_ONLY); this.properties.setLifecycleManagement(LifecycleManagement.START_ONLY);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasDefinedServices()).willReturn(true);
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should().up(any()); then(this.dockerCompose).should().up(any());
@ -199,97 +199,97 @@ class DockerComposeLifecycleManagerTests {
} }
@Test @Test
void startupWhenStartupCommandStartDoesStartupUsingStartAndShutdown() { void startWhenStartCommandStartDoesStartAndStop() {
this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP); this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP);
this.properties.getStartup().setCommand(StartupCommand.START); this.properties.getStart().setCommand(StartCommand.START);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasDefinedServices()).willReturn(true);
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should(never()).up(any()); then(this.dockerCompose).should(never()).up(any());
then(this.dockerCompose).should().start(any()); then(this.dockerCompose).should().start(any());
then(this.dockerCompose).should().down(any()); then(this.dockerCompose).should().stop(any());
then(this.dockerCompose).should(never()).stop(any()); then(this.dockerCompose).should(never()).down(any());
} }
@Test @Test
void startupWhenShutdownCommandStopDoesStartupAndShutdownUsingStop() { void startWhenStopCommandDownDoesStartAndDown() {
this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP); this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP);
this.properties.getShutdown().setCommand(ShutdownCommand.STOP); this.properties.getStop().setCommand(StopCommand.DOWN);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasDefinedServices()).willReturn(true);
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should().up(any()); then(this.dockerCompose).should().up(any());
then(this.dockerCompose).should(never()).start(any()); then(this.dockerCompose).should(never()).start(any());
then(this.dockerCompose).should(never()).down(any()); then(this.dockerCompose).should(never()).stop(any());
then(this.dockerCompose).should().stop(any()); then(this.dockerCompose).should().down(any());
} }
@Test @Test
void startupWhenHasShutdownTimeoutUsesDuration() { void startWhenHasStopTimeoutUsesDuration() {
this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP); this.properties.setLifecycleManagement(LifecycleManagement.START_AND_STOP);
Duration timeout = Duration.ofDays(1); Duration timeout = Duration.ofDays(1);
this.properties.getShutdown().setTimeout(timeout); this.properties.getStop().setTimeout(timeout);
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasDefinedServices()).willReturn(true);
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
then(this.dockerCompose).should().down(timeout); then(this.dockerCompose).should().stop(timeout);
} }
@Test @Test
void startupWhenHasIgnoreLabelIgnoresService() { void startWhenHasIgnoreLabelIgnoresService() {
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(Map.of("org.springframework.boot.ignore", "true")); setUpRunningServices(Map.of("org.springframework.boot.ignore", "true"));
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull(); assertThat(listener.getEvent()).isNotNull();
assertThat(listener.getEvent().getRunningServices()).isEmpty(); assertThat(listener.getEvent().getRunningServices()).isEmpty();
} }
@Test @Test
void startupWaitsUntilReady() { void startWaitsUntilReady() {
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
this.shutdownHandlers.run(); this.shutdownHandlers.run();
then(this.serviceReadinessChecks).should().waitUntilReady(this.runningServices); then(this.serviceReadinessChecks).should().waitUntilReady(this.runningServices);
} }
@Test @Test
void startupGetsDockerComposeWithActiveProfiles() { void startGetsDockerComposeWithActiveProfiles() {
this.properties.getProfiles().setActive(Set.of("my-profile")); this.properties.getProfiles().setActive(Set.of("my-profile"));
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
assertThat(this.activeProfiles).containsExactly("my-profile"); assertThat(this.activeProfiles).containsExactly("my-profile");
} }
@Test @Test
void startupPublishesEvent() { void startPublishesEvent() {
EventCapturingListener listener = new EventCapturingListener(); EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener); this.eventListeners.add(listener);
setupRunningServices(); setUpRunningServices();
this.lifecycleManager.startup(); this.lifecycleManager.start();
DockerComposeServicesReadyEvent event = listener.getEvent(); DockerComposeServicesReadyEvent event = listener.getEvent();
assertThat(event).isNotNull(); assertThat(event).isNotNull();
assertThat(event.getSource()).isEqualTo(this.applicationContext); assertThat(event.getSource()).isEqualTo(this.applicationContext);
assertThat(event.getRunningServices()).isEqualTo(this.runningServices); assertThat(event.getRunningServices()).isEqualTo(this.runningServices);
} }
private void setupRunningServices() { private void setUpRunningServices() {
setupRunningServices(Collections.emptyMap()); setUpRunningServices(Collections.emptyMap());
} }
private void setupRunningServices(Map<String, String> labels) { private void setUpRunningServices(Map<String, String> labels) {
given(this.dockerCompose.hasDefinedServices()).willReturn(true); given(this.dockerCompose.hasDefinedServices()).willReturn(true);
given(this.dockerCompose.hasRunningServices()).willReturn(true); given(this.dockerCompose.hasRunningServices()).willReturn(true);
RunningService runningService = mock(RunningService.class); RunningService runningService = mock(RunningService.class);

@ -53,7 +53,7 @@ class DockerComposeListenerTests {
ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context); ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context);
listener.onApplicationEvent(event); listener.onApplicationEvent(event);
assertThat(listener.getManager()).isNotNull(); assertThat(listener.getManager()).isNotNull();
then(listener.getManager()).should().startup(); then(listener.getManager()).should().start();
} }
static class TestDockerComposeListener extends DockerComposeListener { static class TestDockerComposeListener extends DockerComposeListener {

@ -44,9 +44,9 @@ class DockerComposePropertiesTests {
assertThat(properties.getFile()).isNull(); assertThat(properties.getFile()).isNull();
assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_AND_STOP); assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_AND_STOP);
assertThat(properties.getHost()).isNull(); assertThat(properties.getHost()).isNull();
assertThat(properties.getStartup().getCommand()).isEqualTo(StartupCommand.UP); assertThat(properties.getStart().getCommand()).isEqualTo(StartCommand.UP);
assertThat(properties.getShutdown().getCommand()).isEqualTo(ShutdownCommand.DOWN); assertThat(properties.getStop().getCommand()).isEqualTo(StopCommand.STOP);
assertThat(properties.getShutdown().getTimeout()).isEqualTo(Duration.ofSeconds(10)); assertThat(properties.getStop().getTimeout()).isEqualTo(Duration.ofSeconds(10));
assertThat(properties.getProfiles().getActive()).isEmpty(); assertThat(properties.getProfiles().getActive()).isEmpty();
} }
@ -56,18 +56,18 @@ class DockerComposePropertiesTests {
source.put("spring.docker.compose.file", "my-compose.yml"); source.put("spring.docker.compose.file", "my-compose.yml");
source.put("spring.docker.compose.lifecycle-management", "start-only"); source.put("spring.docker.compose.lifecycle-management", "start-only");
source.put("spring.docker.compose.host", "myhost"); source.put("spring.docker.compose.host", "myhost");
source.put("spring.docker.compose.startup.command", "start"); source.put("spring.docker.compose.start.command", "start");
source.put("spring.docker.compose.shutdown.command", "stop"); source.put("spring.docker.compose.stop.command", "down");
source.put("spring.docker.compose.shutdown.timeout", "5s"); source.put("spring.docker.compose.stop.timeout", "5s");
source.put("spring.docker.compose.profiles.active", "myprofile"); source.put("spring.docker.compose.profiles.active", "myprofile");
Binder binder = new Binder(new MapConfigurationPropertySource(source)); Binder binder = new Binder(new MapConfigurationPropertySource(source));
DockerComposeProperties properties = DockerComposeProperties.get(binder); DockerComposeProperties properties = DockerComposeProperties.get(binder);
assertThat(properties.getFile()).isEqualTo(new File("my-compose.yml")); assertThat(properties.getFile()).isEqualTo(new File("my-compose.yml"));
assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_ONLY); assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_ONLY);
assertThat(properties.getHost()).isEqualTo("myhost"); assertThat(properties.getHost()).isEqualTo("myhost");
assertThat(properties.getStartup().getCommand()).isEqualTo(StartupCommand.START); assertThat(properties.getStart().getCommand()).isEqualTo(StartCommand.START);
assertThat(properties.getShutdown().getCommand()).isEqualTo(ShutdownCommand.STOP); assertThat(properties.getStop().getCommand()).isEqualTo(StopCommand.DOWN);
assertThat(properties.getShutdown().getTimeout()).isEqualTo(Duration.ofSeconds(5)); assertThat(properties.getStop().getTimeout()).isEqualTo(Duration.ofSeconds(5));
assertThat(properties.getProfiles().getActive()).containsExactly("myprofile"); assertThat(properties.getProfiles().getActive()).containsExactly("myprofile");
} }

@ -31,32 +31,32 @@ class LifecycleManagementTests {
@Test @Test
void shouldStartupWhenNone() { void shouldStartupWhenNone() {
assertThat(LifecycleManagement.NONE.shouldStartup()).isFalse(); assertThat(LifecycleManagement.NONE.shouldStart()).isFalse();
} }
@Test @Test
void shouldShutdownWhenNone() { void shouldShutdownWhenNone() {
assertThat(LifecycleManagement.NONE.shouldShutdown()).isFalse(); assertThat(LifecycleManagement.NONE.shouldStop()).isFalse();
} }
@Test @Test
void shouldStartupWhenStartOnly() { void shouldStartupWhenStartOnly() {
assertThat(LifecycleManagement.START_ONLY.shouldStartup()).isTrue(); assertThat(LifecycleManagement.START_ONLY.shouldStart()).isTrue();
} }
@Test @Test
void shouldShutdownWhenStartOnly() { void shouldShutdownWhenStartOnly() {
assertThat(LifecycleManagement.START_ONLY.shouldShutdown()).isFalse(); assertThat(LifecycleManagement.START_ONLY.shouldStop()).isFalse();
} }
@Test @Test
void shouldStartupWhenStartAndStop() { void shouldStartupWhenStartAndStop() {
assertThat(LifecycleManagement.START_AND_STOP.shouldStartup()).isTrue(); assertThat(LifecycleManagement.START_AND_STOP.shouldStart()).isTrue();
} }
@Test @Test
void shouldShutdownWhenStartAndStop() { void shouldShutdownWhenStartAndStop() {
assertThat(LifecycleManagement.START_AND_STOP.shouldShutdown()).isTrue(); assertThat(LifecycleManagement.START_AND_STOP.shouldStop()).isTrue();
} }
} }

@ -25,25 +25,25 @@ import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link StartupCommand}. * Tests for {@link StartCommand}.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
*/ */
class StartupCommandTests { class StartCommandTests {
private DockerCompose dockerCompose = mock(DockerCompose.class); private DockerCompose dockerCompose = mock(DockerCompose.class);
@Test @Test
void applyToWhenUp() { void applyToWhenUp() {
StartupCommand.UP.applyTo(this.dockerCompose, LogLevel.INFO); StartCommand.UP.applyTo(this.dockerCompose, LogLevel.INFO);
then(this.dockerCompose).should().up(LogLevel.INFO); then(this.dockerCompose).should().up(LogLevel.INFO);
} }
@Test @Test
void applyToWhenStart() { void applyToWhenStart() {
StartupCommand.START.applyTo(this.dockerCompose, LogLevel.INFO); StartCommand.START.applyTo(this.dockerCompose, LogLevel.INFO);
then(this.dockerCompose).should().start(LogLevel.INFO); then(this.dockerCompose).should().start(LogLevel.INFO);
} }

@ -26,13 +26,13 @@ import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ShutdownCommand}. * Tests for {@link StopCommand}.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
*/ */
class ShutdownCommandTests { class StopCommandTests {
private DockerCompose dockerCompose = mock(DockerCompose.class); private DockerCompose dockerCompose = mock(DockerCompose.class);
@ -40,13 +40,13 @@ class ShutdownCommandTests {
@Test @Test
void applyToWhenDown() { void applyToWhenDown() {
ShutdownCommand.DOWN.applyTo(this.dockerCompose, this.duration); StopCommand.DOWN.applyTo(this.dockerCompose, this.duration);
then(this.dockerCompose).should().down(this.duration); then(this.dockerCompose).should().down(this.duration);
} }
@Test @Test
void applyToWhenStart() { void applyToWhenStart() {
ShutdownCommand.STOP.applyTo(this.dockerCompose, this.duration); StopCommand.STOP.applyTo(this.dockerCompose, this.duration);
then(this.dockerCompose).should().stop(this.duration); then(this.dockerCompose).should().stop(this.duration);
} }

@ -43,7 +43,7 @@ public abstract class AbstractDockerComposeIntegrationTests {
private final Resource composeResource; private final Resource composeResource;
@AfterAll @AfterAll
static void shutdown() { static void shutDown() {
SpringApplicationShutdownHandlers shutdownHandlers = SpringApplication.getShutdownHandlers(); SpringApplicationShutdownHandlers shutdownHandlers = SpringApplication.getShutdownHandlers();
((Runnable) shutdownHandlers).run(); ((Runnable) shutdownHandlers).run();
} }

@ -169,17 +169,17 @@ TIP: You can also provide your own `ServiceReadinessCheck` implementations and r
[[features.docker-compose.lifecycle]] [[features.docker-compose.lifecycle]]
=== Controlling the Docker Compose Lifecycle === Controlling the Docker Compose Lifecycle
By default Spring Boot calls `docker compose up` when your application starts and `docker compose down` when it's shutdown. By default Spring Boot calls `docker compose up` when your application starts and `docker compose stop` when it's shut down.
If you prefer to have different lifecycle management you can use the configprop:spring.docker.compose.lifecycle-management[] property. If you prefer to have different lifecycle management you can use the configprop:spring.docker.compose.lifecycle-management[] property.
The following values are supported: The following values are supported:
* `none` - Do not start or stop Docker Compose * `none` - Do not start or stop Docker Compose
* `start-only` - Start Docker Compose on application startup and leave it running * `start-only` - Start Docker Compose when the application starts and leave it running
* `start-and-stop` - Start Docker Compose on application startup and stop it on application shutdown * `start-and-stop` - Start Docker Compose whe the application starts and stop it when the JVM exits
In addition you can use the configprop:spring.docker.compose.startup.command[] property to change if `docker up` or `docker start` is used. In addition you can use the configprop:spring.docker.compose.start.command[] property to change whether `docker compose up` or `docker compose start` is used.
The configprop:spring.docker.compose.shutdown.command[] allows you to configure if `docker down` or `docker stop` is used. The configprop:spring.docker.compose.stop.command[] allows you to configure if `docker compose down` or `docker compose stop` is used.
The following example shows how lifecycle management can be configured: The following example shows how lifecycle management can be configured:
@ -189,10 +189,10 @@ The following example shows how lifecycle management can be configured:
docker: docker:
compose: compose:
lifecycle-management: start-and-stop lifecycle-management: start-and-stop
startup: start:
command: start command: start
shutdown: stop:
command: stop command: down
timeout: 1m timeout: 1m
---- ----

Loading…
Cancel
Save