commit
59a4018cbe
|
@ -14,3 +14,4 @@ spring.template.provider.cache=false
|
||||||
spring.thymeleaf.cache=false
|
spring.thymeleaf.cache=false
|
||||||
spring.web.resources.cache.period=0
|
spring.web.resources.cache.period=0
|
||||||
spring.web.resources.chain.cache=false
|
spring.web.resources.chain.cache=false
|
||||||
|
spring.docker.compose.readiness.wait=only-if-started
|
||||||
|
|
|
@ -69,11 +69,6 @@ class DefaultDockerCompose implements DockerCompose {
|
||||||
return !this.cli.run(new DockerCliCommand.ComposeConfig()).services().isEmpty();
|
return !this.cli.run(new DockerCliCommand.ComposeConfig()).services().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasRunningServices() {
|
|
||||||
return runComposePs().stream().anyMatch(this::isRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RunningService> getRunningServices() {
|
public List<RunningService> getRunningServices() {
|
||||||
List<DockerCliComposePsResponse> runningPsResponses = runComposePs().stream().filter(this::isRunning).toList();
|
List<DockerCliComposePsResponse> runningPsResponses = runComposePs().stream().filter(this::isRunning).toList();
|
||||||
|
|
|
@ -19,7 +19,9 @@ package org.springframework.boot.docker.compose.core;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@ -39,13 +41,13 @@ import org.springframework.core.log.LogMessage;
|
||||||
*/
|
*/
|
||||||
class DockerCli {
|
class DockerCli {
|
||||||
|
|
||||||
|
private static final Map<File, DockerCommands> dockerCommandsCache = new HashMap<>();
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(DockerCli.class);
|
private static final Log logger = LogFactory.getLog(DockerCli.class);
|
||||||
|
|
||||||
private final ProcessRunner processRunner;
|
private final ProcessRunner processRunner;
|
||||||
|
|
||||||
private final List<String> dockerCommand;
|
private final DockerCommands dockerCommands;
|
||||||
|
|
||||||
private final List<String> dockerComposeCommand;
|
|
||||||
|
|
||||||
private final DockerComposeFile composeFile;
|
private final DockerComposeFile composeFile;
|
||||||
|
|
||||||
|
@ -59,12 +61,75 @@ class DockerCli {
|
||||||
*/
|
*/
|
||||||
DockerCli(File workingDirectory, DockerComposeFile composeFile, Set<String> activeProfiles) {
|
DockerCli(File workingDirectory, DockerComposeFile composeFile, Set<String> activeProfiles) {
|
||||||
this.processRunner = new ProcessRunner(workingDirectory);
|
this.processRunner = new ProcessRunner(workingDirectory);
|
||||||
this.dockerCommand = getDockerCommand(this.processRunner);
|
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
|
||||||
this.dockerComposeCommand = getDockerComposeCommand(this.processRunner);
|
(key) -> new DockerCommands(this.processRunner));
|
||||||
this.composeFile = composeFile;
|
this.composeFile = composeFile;
|
||||||
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
|
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the given {@link DockerCli} command and return the response.
|
||||||
|
* @param <R> the response type
|
||||||
|
* @param dockerCommand the command to run
|
||||||
|
* @return the response
|
||||||
|
*/
|
||||||
|
<R> R run(DockerCliCommand<R> dockerCommand) {
|
||||||
|
List<String> command = createCommand(dockerCommand.getType());
|
||||||
|
command.addAll(dockerCommand.getCommand());
|
||||||
|
Consumer<String> outputConsumer = createOutputConsumer(dockerCommand.getLogLevel());
|
||||||
|
String json = this.processRunner.run(outputConsumer, command.toArray(new String[0]));
|
||||||
|
return dockerCommand.deserialize(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<String> createOutputConsumer(LogLevel logLevel) {
|
||||||
|
if (logLevel == null || logLevel == LogLevel.OFF) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (line) -> logLevel.log(logger, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> createCommand(Type type) {
|
||||||
|
return switch (type) {
|
||||||
|
case DOCKER -> new ArrayList<>(this.dockerCommands.get(type));
|
||||||
|
case DOCKER_COMPOSE -> {
|
||||||
|
List<String> result = new ArrayList<>(this.dockerCommands.get(type));
|
||||||
|
if (this.composeFile != null) {
|
||||||
|
result.add("--file");
|
||||||
|
result.add(this.composeFile.toString());
|
||||||
|
}
|
||||||
|
result.add("--ansi");
|
||||||
|
result.add("never");
|
||||||
|
for (String profile : this.activeProfiles) {
|
||||||
|
result.add("--profile");
|
||||||
|
result.add(profile);
|
||||||
|
}
|
||||||
|
yield result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link DockerComposeFile} being used by this CLI instance.
|
||||||
|
* @return the docker compose file
|
||||||
|
*/
|
||||||
|
DockerComposeFile getDockerComposeFile() {
|
||||||
|
return this.composeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds details of the actual CLI commands to invoke.
|
||||||
|
*/
|
||||||
|
private static class DockerCommands {
|
||||||
|
|
||||||
|
private final List<String> dockerCommand;
|
||||||
|
|
||||||
|
private final List<String> dockerComposeCommand;
|
||||||
|
|
||||||
|
DockerCommands(ProcessRunner processRunner) {
|
||||||
|
this.dockerCommand = getDockerCommand(processRunner);
|
||||||
|
this.dockerComposeCommand = getDockerComposeCommand(processRunner);
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> getDockerCommand(ProcessRunner processRunner) {
|
private List<String> getDockerCommand(ProcessRunner processRunner) {
|
||||||
try {
|
try {
|
||||||
String version = processRunner.run("docker", "version", "--format", "{{.Client.Version}}");
|
String version = processRunner.run("docker", "version", "--format", "{{.Client.Version}}");
|
||||||
|
@ -72,7 +137,8 @@ class DockerCli {
|
||||||
return List.of("docker");
|
return List.of("docker");
|
||||||
}
|
}
|
||||||
catch (ProcessStartException ex) {
|
catch (ProcessStartException ex) {
|
||||||
throw new DockerProcessStartException("Unable to start docker process. Is docker correctly installed?", ex);
|
throw new DockerProcessStartException("Unable to start docker process. Is docker correctly installed?",
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
catch (ProcessExitException ex) {
|
catch (ProcessExitException ex) {
|
||||||
if (ex.getStdErr().contains("docker daemon is not running")
|
if (ex.getStdErr().contains("docker daemon is not running")
|
||||||
|
@ -80,7 +146,6 @@ class DockerCli {
|
||||||
throw new DockerNotRunningException(ex.getStdErr(), ex);
|
throw new DockerNotRunningException(ex.getStdErr(), ex);
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,53 +174,13 @@ class DockerCli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
List<String> get(Type type) {
|
||||||
* Run the given {@link DockerCli} command and return the response.
|
|
||||||
* @param <R> the response type
|
|
||||||
* @param dockerCommand the command to run
|
|
||||||
* @return the response
|
|
||||||
*/
|
|
||||||
<R> R run(DockerCliCommand<R> dockerCommand) {
|
|
||||||
List<String> command = createCommand(dockerCommand.getType());
|
|
||||||
command.addAll(dockerCommand.getCommand());
|
|
||||||
Consumer<String> outputConsumer = createOutputConsumer(dockerCommand.getLogLevel());
|
|
||||||
String json = this.processRunner.run(outputConsumer, command.toArray(new String[0]));
|
|
||||||
return dockerCommand.deserialize(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Consumer<String> createOutputConsumer(LogLevel logLevel) {
|
|
||||||
if (logLevel == null || logLevel == LogLevel.OFF) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (line) -> logLevel.log(logger, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> createCommand(Type type) {
|
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
case DOCKER -> new ArrayList<>(this.dockerCommand);
|
case DOCKER -> this.dockerCommand;
|
||||||
case DOCKER_COMPOSE -> {
|
case DOCKER_COMPOSE -> this.dockerComposeCommand;
|
||||||
List<String> result = new ArrayList<>(this.dockerComposeCommand);
|
|
||||||
if (this.composeFile != null) {
|
|
||||||
result.add("--file");
|
|
||||||
result.add(this.composeFile.toString());
|
|
||||||
}
|
|
||||||
result.add("--ansi");
|
|
||||||
result.add("never");
|
|
||||||
for (String profile : this.activeProfiles) {
|
|
||||||
result.add("--profile");
|
|
||||||
result.add(profile);
|
|
||||||
}
|
|
||||||
yield result;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link DockerComposeFile} being used by this CLI instance.
|
|
||||||
* @return the docker compose file
|
|
||||||
*/
|
|
||||||
DockerComposeFile getDockerComposeFile() {
|
|
||||||
return this.composeFile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,15 +73,6 @@ public interface DockerCompose {
|
||||||
*/
|
*/
|
||||||
boolean hasDefinedServices();
|
boolean hasDefinedServices();
|
||||||
|
|
||||||
/**
|
|
||||||
* Return if services defined in the {@link DockerComposeFile} for the active profile
|
|
||||||
* are running.
|
|
||||||
* @return {@code true} if services are running
|
|
||||||
* @see #hasDefinedServices()
|
|
||||||
* @see #getRunningServices()
|
|
||||||
*/
|
|
||||||
boolean hasRunningServices();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the running services for the active profile, or an empty list if no services
|
* Return the running services for the active profile, or an empty list if no services
|
||||||
* are running.
|
* are running.
|
||||||
|
|
|
@ -112,19 +112,21 @@ class DockerComposeLifecycleManager {
|
||||||
Start start = this.properties.getStart();
|
Start start = this.properties.getStart();
|
||||||
Stop stop = this.properties.getStop();
|
Stop stop = this.properties.getStop();
|
||||||
Wait wait = this.properties.getReadiness().getWait();
|
Wait wait = this.properties.getReadiness().getWait();
|
||||||
if (lifecycleManagement.shouldStart() && !dockerCompose.hasRunningServices()) {
|
List<RunningService> runningServices = dockerCompose.getRunningServices();
|
||||||
|
if (lifecycleManagement.shouldStart() && runningServices.isEmpty()) {
|
||||||
start.getCommand().applyTo(dockerCompose, start.getLogLevel());
|
start.getCommand().applyTo(dockerCompose, start.getLogLevel());
|
||||||
|
runningServices = dockerCompose.getRunningServices();
|
||||||
wait = (wait != Wait.ONLY_IF_STARTED) ? wait : Wait.ALWAYS;
|
wait = (wait != Wait.ONLY_IF_STARTED) ? wait : Wait.ALWAYS;
|
||||||
if (lifecycleManagement.shouldStop()) {
|
if (lifecycleManagement.shouldStop()) {
|
||||||
this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
|
this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<RunningService> runningServices = new ArrayList<>(dockerCompose.getRunningServices());
|
List<RunningService> relevantServices = new ArrayList<>(runningServices);
|
||||||
runningServices.removeIf(this::isIgnored);
|
relevantServices.removeIf(this::isIgnored);
|
||||||
if (wait == Wait.ALWAYS || wait == null) {
|
if (wait == Wait.ALWAYS || wait == null) {
|
||||||
this.serviceReadinessChecks.waitUntilReady(runningServices);
|
this.serviceReadinessChecks.waitUntilReady(relevantServices);
|
||||||
}
|
}
|
||||||
publishEvent(new DockerComposeServicesReadyEvent(this.applicationContext, runningServices));
|
publishEvent(new DockerComposeServicesReadyEvent(this.applicationContext, relevantServices));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DockerComposeFile getComposeFile() {
|
protected DockerComposeFile getComposeFile() {
|
||||||
|
|
|
@ -96,26 +96,6 @@ class DefaultDockerComposeTests {
|
||||||
assertThat(compose.hasDefinedServices()).isTrue();
|
assertThat(compose.hasDefinedServices()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void hasRunningServicesWhenPsListsRunningServiceReturnsTrue() {
|
|
||||||
willReturn(List.of(new DockerCliComposePsResponse("id", "name", "image", "exited"),
|
|
||||||
new DockerCliComposePsResponse("id", "name", "image", "running")))
|
|
||||||
.given(this.cli)
|
|
||||||
.run(new DockerCliCommand.ComposePs());
|
|
||||||
DefaultDockerCompose compose = new DefaultDockerCompose(this.cli, HOST);
|
|
||||||
assertThat(compose.hasRunningServices()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void hasRunningServicesWhenPsListReturnsAllExitedReturnsFalse() {
|
|
||||||
willReturn(List.of(new DockerCliComposePsResponse("id", "name", "image", "exited"),
|
|
||||||
new DockerCliComposePsResponse("id", "name", "image", "running")))
|
|
||||||
.given(this.cli)
|
|
||||||
.run(new DockerCliCommand.ComposePs());
|
|
||||||
DefaultDockerCompose compose = new DefaultDockerCompose(this.cli, HOST);
|
|
||||||
assertThat(compose.hasRunningServices()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getRunningServicesReturnsServices() {
|
void getRunningServicesReturnsServices() {
|
||||||
String id = "123";
|
String id = "123";
|
||||||
|
|
|
@ -351,7 +351,6 @@ class DockerComposeLifecycleManagerTests {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void setUpRunningServices(boolean started, Map<String, String> labels) {
|
private void setUpRunningServices(boolean started, Map<String, String> labels) {
|
||||||
given(this.dockerCompose.hasDefinedServices()).willReturn(true);
|
given(this.dockerCompose.hasDefinedServices()).willReturn(true);
|
||||||
given(this.dockerCompose.hasRunningServices()).willReturn(true);
|
|
||||||
RunningService runningService = mock(RunningService.class);
|
RunningService runningService = mock(RunningService.class);
|
||||||
given(runningService.labels()).willReturn(labels);
|
given(runningService.labels()).willReturn(labels);
|
||||||
this.runningServices = List.of(runningService);
|
this.runningServices = List.of(runningService);
|
||||||
|
|
Loading…
Reference in New Issue