Skip to content

Commit

Permalink
Merge branch 'gh-35435'
Browse files Browse the repository at this point in the history
Closes gh-35435
  • Loading branch information
philwebb committed May 17, 2023
2 parents a8602a1 + 9bacf42 commit 59a4018
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ spring.template.provider.cache=false
spring.thymeleaf.cache=false
spring.web.resources.cache.period=0
spring.web.resources.chain.cache=false
spring.docker.compose.readiness.wait=only-if-started
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ public boolean hasDefinedServices() {
return !this.cli.run(new DockerCliCommand.ComposeConfig()).services().isEmpty();
}

@Override
public boolean hasRunningServices() {
return runComposePs().stream().anyMatch(this::isRunning);
}

@Override
public List<RunningService> getRunningServices() {
List<DockerCliComposePsResponse> runningPsResponses = runComposePs().stream().filter(this::isRunning).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

Expand All @@ -39,13 +41,13 @@
*/
class DockerCli {

private static final Map<File, DockerCommands> dockerCommandsCache = new HashMap<>();

private static final Log logger = LogFactory.getLog(DockerCli.class);

private final ProcessRunner processRunner;

private final List<String> dockerCommand;

private final List<String> dockerComposeCommand;
private final DockerCommands dockerCommands;

private final DockerComposeFile composeFile;

Expand All @@ -59,56 +61,12 @@ class DockerCli {
*/
DockerCli(File workingDirectory, DockerComposeFile composeFile, Set<String> activeProfiles) {
this.processRunner = new ProcessRunner(workingDirectory);
this.dockerCommand = getDockerCommand(this.processRunner);
this.dockerComposeCommand = getDockerComposeCommand(this.processRunner);
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
(key) -> new DockerCommands(this.processRunner));
this.composeFile = composeFile;
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
}

private List<String> getDockerCommand(ProcessRunner processRunner) {
try {
String version = processRunner.run("docker", "version", "--format", "{{.Client.Version}}");
logger.trace(LogMessage.format("Using docker %s", version));
return List.of("docker");
}
catch (ProcessStartException ex) {
throw new DockerProcessStartException("Unable to start docker process. Is docker correctly installed?", ex);
}
catch (ProcessExitException ex) {
if (ex.getStdErr().contains("docker daemon is not running")
|| ex.getStdErr().contains("Cannot connect to the Docker daemon")) {
throw new DockerNotRunningException(ex.getStdErr(), ex);
}
throw ex;

}
}

private List<String> getDockerComposeCommand(ProcessRunner processRunner) {
try {
DockerCliComposeVersionResponse response = DockerJson.deserialize(
processRunner.run("docker", "compose", "version", "--format", "json"),
DockerCliComposeVersionResponse.class);
logger.trace(LogMessage.format("Using docker compose %s", response.version()));
return List.of("docker", "compose");
}
catch (ProcessExitException ex) {
// Ignore and try docker-compose
}
try {
DockerCliComposeVersionResponse response = DockerJson.deserialize(
processRunner.run("docker-compose", "version", "--format", "json"),
DockerCliComposeVersionResponse.class);
logger.trace(LogMessage.format("Using docker-compose %s", response.version()));
return List.of("docker-compose");
}
catch (ProcessStartException ex) {
throw new DockerProcessStartException(
"Unable to start 'docker-compose' process or use 'docker compose'. Is docker correctly installed?",
ex);
}
}

/**
* Run the given {@link DockerCli} command and return the response.
* @param <R> the response type
Expand All @@ -132,9 +90,9 @@ private Consumer<String> createOutputConsumer(LogLevel logLevel) {

private List<String> createCommand(Type type) {
return switch (type) {
case DOCKER -> new ArrayList<>(this.dockerCommand);
case DOCKER -> new ArrayList<>(this.dockerCommands.get(type));
case DOCKER_COMPOSE -> {
List<String> result = new ArrayList<>(this.dockerComposeCommand);
List<String> result = new ArrayList<>(this.dockerCommands.get(type));
if (this.composeFile != null) {
result.add("--file");
result.add(this.composeFile.toString());
Expand All @@ -158,4 +116,71 @@ 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) {
try {
String version = processRunner.run("docker", "version", "--format", "{{.Client.Version}}");
logger.trace(LogMessage.format("Using docker %s", version));
return List.of("docker");
}
catch (ProcessStartException ex) {
throw new DockerProcessStartException("Unable to start docker process. Is docker correctly installed?",
ex);
}
catch (ProcessExitException ex) {
if (ex.getStdErr().contains("docker daemon is not running")
|| ex.getStdErr().contains("Cannot connect to the Docker daemon")) {
throw new DockerNotRunningException(ex.getStdErr(), ex);
}
throw ex;
}
}

private List<String> getDockerComposeCommand(ProcessRunner processRunner) {
try {
DockerCliComposeVersionResponse response = DockerJson.deserialize(
processRunner.run("docker", "compose", "version", "--format", "json"),
DockerCliComposeVersionResponse.class);
logger.trace(LogMessage.format("Using docker compose %s", response.version()));
return List.of("docker", "compose");
}
catch (ProcessExitException ex) {
// Ignore and try docker-compose
}
try {
DockerCliComposeVersionResponse response = DockerJson.deserialize(
processRunner.run("docker-compose", "version", "--format", "json"),
DockerCliComposeVersionResponse.class);
logger.trace(LogMessage.format("Using docker-compose %s", response.version()));
return List.of("docker-compose");
}
catch (ProcessStartException ex) {
throw new DockerProcessStartException(
"Unable to start 'docker-compose' process or use 'docker compose'. Is docker correctly installed?",
ex);
}
}

List<String> get(Type type) {
return switch (type) {
case DOCKER -> this.dockerCommand;
case DOCKER_COMPOSE -> this.dockerComposeCommand;
};
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,6 @@ public interface DockerCompose {
*/
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
* are running.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,21 @@ void start() {
Start start = this.properties.getStart();
Stop stop = this.properties.getStop();
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());
runningServices = dockerCompose.getRunningServices();
wait = (wait != Wait.ONLY_IF_STARTED) ? wait : Wait.ALWAYS;
if (lifecycleManagement.shouldStop()) {
this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
}
}
List<RunningService> runningServices = new ArrayList<>(dockerCompose.getRunningServices());
runningServices.removeIf(this::isIgnored);
List<RunningService> relevantServices = new ArrayList<>(runningServices);
relevantServices.removeIf(this::isIgnored);
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,6 @@ void hasDefinedServicesWhenComposeConfigServicesIsNotEmptyReturnsTrue() {
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
void getRunningServicesReturnsServices() {
String id = "123";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,6 @@ private void setUpRunningServices(boolean started) {
@SuppressWarnings("unchecked")
private void setUpRunningServices(boolean started, Map<String, String> 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);
Expand Down

0 comments on commit 59a4018

Please sign in to comment.