Support multiple Docker Compose files

Closes gh-41691
This commit is contained in:
Moritz Halbritter
2024-08-05 12:08:03 +02:00
parent c7e29b7b1b
commit 4eebb8e629
11 changed files with 161 additions and 48 deletions

View File

@@ -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.
@@ -94,8 +94,10 @@ class DockerCli {
case DOCKER_COMPOSE -> {
List<String> result = new ArrayList<>(this.dockerCommands.get(type));
if (this.composeFile != null) {
result.add("--file");
result.add(this.composeFile.toString());
for (File file : this.composeFile.getFiles()) {
result.add("--file");
result.add(file.getPath());
}
}
result.add("--ansi");
result.add("never");

View File

@@ -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.
@@ -21,7 +21,10 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
@@ -33,6 +36,7 @@ import org.springframework.util.Assert;
* @author Phillip Webb
* @since 3.1.0
* @see #of(File)
* @see #of(Collection)
* @see #find(File)
*/
public final class DockerComposeFile {
@@ -40,17 +44,31 @@ public final class DockerComposeFile {
private static final List<String> SEARCH_ORDER = List.of("compose.yaml", "compose.yml", "docker-compose.yaml",
"docker-compose.yml");
private final File file;
private final List<File> files;
private DockerComposeFile(File file) {
private DockerComposeFile(List<File> files) {
Assert.state(!files.isEmpty(), "Files must not be empty");
this.files = files.stream().map(DockerComposeFile::toCanonicalFile).toList();
}
private static File toCanonicalFile(File file) {
try {
this.file = file.getCanonicalFile();
return file.getCanonicalFile();
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Returns the source docker compose files.
* @return the source docker compose files
* @since 3.4.0
*/
public List<File> getFiles() {
return this.files;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -60,17 +78,20 @@ public final class DockerComposeFile {
return false;
}
DockerComposeFile other = (DockerComposeFile) obj;
return this.file.equals(other.file);
return this.files.equals(other.files);
}
@Override
public int hashCode() {
return this.file.hashCode();
return this.files.hashCode();
}
@Override
public String toString() {
return this.file.toString();
if (this.files.size() == 1) {
return this.files.get(0).getPath();
}
return this.files.stream().map(File::toString).collect(Collectors.joining(", "));
}
/**
@@ -111,7 +132,23 @@ public final class DockerComposeFile {
Assert.notNull(file, "File must not be null");
Assert.isTrue(file.exists(), () -> "Docker Compose file '%s' does not exist".formatted(file));
Assert.isTrue(file.isFile(), () -> "Docker compose file '%s' is not a file".formatted(file));
return new DockerComposeFile(file);
return new DockerComposeFile(Collections.singletonList(file));
}
/**
* Creates a new {@link DockerComposeFile} for the given {@link File files}.
* @param files the source files
* @return the docker compose file
* @since 3.4.0
*/
public static DockerComposeFile of(Collection<? extends File> files) {
Assert.notNull(files, "Files must not be null");
for (File file : files) {
Assert.notNull(file, "File must not be null");
Assert.isTrue(file.exists(), () -> "Docker Compose file '%s' does not exist".formatted(file));
Assert.isTrue(file.isFile(), () -> "Docker compose file '%s' is not a file".formatted(file));
}
return new DockerComposeFile(List.copyOf(files));
}
}

View File

@@ -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.
@@ -31,7 +31,7 @@ public record DockerComposeOrigin(DockerComposeFile composeFile, String serviceN
@Override
public String toString() {
return "Docker compose service '%s' defined in '%s'".formatted(this.serviceName,
return "Docker compose service '%s' defined in %s".formatted(this.serviceName,
(this.composeFile != null) ? this.composeFile : "default compose file");
}

View File

@@ -39,6 +39,7 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Manages the lifecycle for docker compose services.
@@ -110,7 +111,7 @@ class DockerComposeLifecycleManager {
Set<String> activeProfiles = this.properties.getProfiles().getActive();
DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles);
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));
return;
}
@@ -145,11 +146,16 @@ class DockerComposeLifecycleManager {
}
protected DockerComposeFile getComposeFile() {
DockerComposeFile composeFile = (this.properties.getFile() != null)
? DockerComposeFile.of(this.properties.getFile()) : DockerComposeFile.find(this.workingDirectory);
DockerComposeFile composeFile = (CollectionUtils.isEmpty(this.properties.getFile()))
? DockerComposeFile.find(this.workingDirectory) : DockerComposeFile.of(this.properties.getFile());
Assert.state(composeFile != null, () -> "No Docker Compose file found in directory '%s'".formatted(
((this.workingDirectory != null) ? this.workingDirectory : new File(".")).toPath().toAbsolutePath()));
logger.info(LogMessage.format("Using Docker Compose file '%s'", composeFile));
if (composeFile.getFiles().size() == 1) {
logger.info(LogMessage.format("Using Docker Compose file %s", composeFile.getFiles().get(0)));
}
else {
logger.info(LogMessage.format("Using Docker Compose files %s", composeFile.toString()));
}
return composeFile;
}

View File

@@ -49,7 +49,7 @@ public class DockerComposeProperties {
/**
* Path to a specific docker compose configuration file.
*/
private File file;
private final List<File> file = new ArrayList<>();
/**
* Docker compose lifecycle management.
@@ -88,14 +88,10 @@ public class DockerComposeProperties {
this.enabled = enabled;
}
public File getFile() {
public List<File> getFile() {
return this.file;
}
public void setFile(File file) {
this.file = file;
}
public LifecycleManagement getLifecycleManagement() {
return this.lifecycleManagement;
}