Merge pull request #42571 from nosan
* pr/42571: Polish "Add property to specify Docker Compose flags" Add property to specify Docker Compose flags Closes gh-42571
This commit is contained in:
@@ -24,10 +24,13 @@ import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.docker.compose.core.DockerCli.DockerComposeOptions;
|
||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeConfig;
|
||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeDown;
|
||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposePs;
|
||||
@@ -60,7 +63,7 @@ class DockerCliIntegrationTests {
|
||||
|
||||
@Test
|
||||
void runBasicCommand() {
|
||||
DockerCli cli = new DockerCli(null, null, Collections.emptySet());
|
||||
DockerCli cli = new DockerCli(null, null);
|
||||
List<DockerCliContextResponse> context = cli.run(new DockerCliCommand.Context());
|
||||
assertThat(context).isNotEmpty();
|
||||
}
|
||||
@@ -68,7 +71,9 @@ class DockerCliIntegrationTests {
|
||||
@Test
|
||||
void runLifecycle() throws IOException {
|
||||
File composeFile = createComposeFile("redis-compose.yaml");
|
||||
DockerCli cli = new DockerCli(null, DockerComposeFile.of(composeFile), Collections.emptySet());
|
||||
String projectName = UUID.randomUUID().toString();
|
||||
DockerCli cli = new DockerCli(null, new DockerComposeOptions(DockerComposeFile.of(composeFile),
|
||||
Collections.emptySet(), List.of("--project-name=" + projectName)));
|
||||
try {
|
||||
// Verify that no services are running (this is a fresh compose project)
|
||||
List<DockerCliComposePsResponse> ps = cli.run(new ComposePs());
|
||||
@@ -76,6 +81,7 @@ class DockerCliIntegrationTests {
|
||||
// List the config and verify that redis is there
|
||||
DockerCliComposeConfigResponse config = cli.run(new ComposeConfig());
|
||||
assertThat(config.services()).containsOnlyKeys("redis");
|
||||
assertThat(config.name()).isEqualTo(projectName);
|
||||
// Run up
|
||||
cli.run(new ComposeUp(LogLevel.INFO, Collections.emptyList()));
|
||||
// Run ps and use id to run inspect on the id
|
||||
@@ -106,7 +112,8 @@ class DockerCliIntegrationTests {
|
||||
@Test
|
||||
void shouldWorkWithMultipleComposeFiles() throws IOException {
|
||||
List<File> composeFiles = createComposeFiles();
|
||||
DockerCli cli = new DockerCli(null, DockerComposeFile.of(composeFiles), Collections.emptySet());
|
||||
DockerCli cli = new DockerCli(null,
|
||||
new DockerComposeOptions(DockerComposeFile.of(composeFiles), Set.of("dev"), Collections.emptyList()));
|
||||
try {
|
||||
// List the config and verify that both redis are there
|
||||
DockerCliComposeConfigResponse config = cli.run(new ComposeConfig());
|
||||
@@ -146,7 +153,8 @@ class DockerCliIntegrationTests {
|
||||
private static List<File> createComposeFiles() throws IOException {
|
||||
File file1 = createComposeFile("1.yaml");
|
||||
File file2 = createComposeFile("2.yaml");
|
||||
return List.of(file1, file2);
|
||||
File file3 = createComposeFile("3.yaml");
|
||||
return List.of(file1, file2, file3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
services:
|
||||
redis1:
|
||||
profiles: [ 'dev' ]
|
||||
image: '{imageName}'
|
||||
ports:
|
||||
- '6379'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
redis3:
|
||||
profiles: [ 'prod' ]
|
||||
image: '{imageName}'
|
||||
ports:
|
||||
- '6379'
|
||||
@@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.Type;
|
||||
import org.springframework.boot.logging.LogLevel;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Wrapper around {@code docker} and {@code docker-compose} command line tools.
|
||||
@@ -49,22 +50,18 @@ class DockerCli {
|
||||
|
||||
private final DockerCommands dockerCommands;
|
||||
|
||||
private final DockerComposeFile composeFile;
|
||||
|
||||
private final Set<String> activeProfiles;
|
||||
private final DockerComposeOptions dockerComposeOptions;
|
||||
|
||||
/**
|
||||
* Create a new {@link DockerCli} instance.
|
||||
* @param workingDirectory the working directory or {@code null}
|
||||
* @param composeFile the Docker Compose file to use
|
||||
* @param activeProfiles the Docker Compose profiles to activate
|
||||
* @param dockerComposeOptions the Docker Compose options to use or {@code null}.
|
||||
*/
|
||||
DockerCli(File workingDirectory, DockerComposeFile composeFile, Set<String> activeProfiles) {
|
||||
DockerCli(File workingDirectory, DockerComposeOptions dockerComposeOptions) {
|
||||
this.processRunner = new ProcessRunner(workingDirectory);
|
||||
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
|
||||
(key) -> new DockerCommands(this.processRunner));
|
||||
this.composeFile = composeFile;
|
||||
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
|
||||
this.dockerComposeOptions = (dockerComposeOptions != null) ? dockerComposeOptions : DockerComposeOptions.none();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,17 +90,25 @@ class DockerCli {
|
||||
case DOCKER -> new ArrayList<>(this.dockerCommands.get(type));
|
||||
case DOCKER_COMPOSE -> {
|
||||
List<String> result = new ArrayList<>(this.dockerCommands.get(type));
|
||||
if (this.composeFile != null) {
|
||||
for (File file : this.composeFile.getFiles()) {
|
||||
DockerComposeFile composeFile = this.dockerComposeOptions.composeFile();
|
||||
if (composeFile != null) {
|
||||
for (File file : composeFile.getFiles()) {
|
||||
result.add("--file");
|
||||
result.add(file.getPath());
|
||||
}
|
||||
}
|
||||
result.add("--ansi");
|
||||
result.add("never");
|
||||
for (String profile : this.activeProfiles) {
|
||||
result.add("--profile");
|
||||
result.add(profile);
|
||||
Set<String> activeProfiles = this.dockerComposeOptions.activeProfiles();
|
||||
if (!CollectionUtils.isEmpty(activeProfiles)) {
|
||||
for (String profile : activeProfiles) {
|
||||
result.add("--profile");
|
||||
result.add(profile);
|
||||
}
|
||||
}
|
||||
List<String> arguments = this.dockerComposeOptions.arguments();
|
||||
if (!CollectionUtils.isEmpty(arguments)) {
|
||||
result.addAll(arguments);
|
||||
}
|
||||
yield result;
|
||||
}
|
||||
@@ -115,7 +120,7 @@ class DockerCli {
|
||||
* @return the Docker Compose file
|
||||
*/
|
||||
DockerComposeFile getDockerComposeFile() {
|
||||
return this.composeFile;
|
||||
return this.dockerComposeOptions.composeFile();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,4 +190,22 @@ class DockerCli {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for Docker Compose.
|
||||
*
|
||||
* @param composeFile the Docker Compose file to use
|
||||
* @param activeProfiles the profiles to activate
|
||||
* @param arguments the arguments to pass to Docker Compose
|
||||
*/
|
||||
record DockerComposeOptions(DockerComposeFile composeFile, Set<String> activeProfiles, List<String> arguments) {
|
||||
DockerComposeOptions {
|
||||
activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
|
||||
arguments = (arguments != null) ? arguments : Collections.emptyList();
|
||||
}
|
||||
|
||||
static DockerComposeOptions none() {
|
||||
return new DockerComposeOptions(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.boot.docker.compose.core;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.docker.compose.core.DockerCli.DockerComposeOptions;
|
||||
import org.springframework.boot.logging.LogLevel;
|
||||
|
||||
/**
|
||||
@@ -125,7 +127,22 @@ public interface DockerCompose {
|
||||
* @return a {@link DockerCompose} instance
|
||||
*/
|
||||
static DockerCompose get(DockerComposeFile file, String hostname, Set<String> activeProfiles) {
|
||||
DockerCli cli = new DockerCli(null, file, activeProfiles);
|
||||
return get(file, hostname, activeProfiles, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to create a {@link DockerCompose} instance.
|
||||
* @param file the Docker Compose file
|
||||
* @param hostname the hostname used for services or {@code null} if the hostname
|
||||
* should be deduced
|
||||
* @param activeProfiles a set of the profiles that should be activated
|
||||
* @param arguments the arguments to pass to Docker Compose
|
||||
* @return a {@link DockerCompose} instance
|
||||
* @since 3.4.0
|
||||
*/
|
||||
static DockerCompose get(DockerComposeFile file, String hostname, Set<String> activeProfiles,
|
||||
List<String> arguments) {
|
||||
DockerCli cli = new DockerCli(null, new DockerComposeOptions(file, activeProfiles, arguments));
|
||||
return new DefaultDockerCompose(cli, hostname);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ class DockerComposeLifecycleManager {
|
||||
}
|
||||
DockerComposeFile composeFile = getComposeFile();
|
||||
Set<String> activeProfiles = this.properties.getProfiles().getActive();
|
||||
DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles);
|
||||
List<String> arguments = this.properties.getArguments();
|
||||
DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles, arguments);
|
||||
if (!dockerCompose.hasDefinedServices()) {
|
||||
logger.warn(LogMessage.format("No services defined in Docker Compose file %s with active profiles %s",
|
||||
composeFile, activeProfiles));
|
||||
@@ -159,8 +160,9 @@ class DockerComposeLifecycleManager {
|
||||
return composeFile;
|
||||
}
|
||||
|
||||
protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set<String> activeProfiles) {
|
||||
return DockerCompose.get(composeFile, this.properties.getHost(), activeProfiles);
|
||||
protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set<String> activeProfiles,
|
||||
List<String> arguments) {
|
||||
return DockerCompose.get(composeFile, this.properties.getHost(), activeProfiles, arguments);
|
||||
}
|
||||
|
||||
private boolean isIgnored(RunningService service) {
|
||||
|
||||
@@ -46,6 +46,11 @@ public class DockerComposeProperties {
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Arguments to pass to the Docker Compose command.
|
||||
*/
|
||||
private final List<String> arguments = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Paths to the Docker Compose configuration files.
|
||||
*/
|
||||
@@ -88,6 +93,10 @@ public class DockerComposeProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public List<String> getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
public List<File> getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ class DockerComposeLifecycleManagerTests {
|
||||
|
||||
private Set<String> activeProfiles;
|
||||
|
||||
private List<String> arguments;
|
||||
|
||||
private GenericApplicationContext applicationContext;
|
||||
|
||||
private TestSpringApplicationShutdownHandlers shutdownHandlers;
|
||||
@@ -358,6 +360,14 @@ class DockerComposeLifecycleManagerTests {
|
||||
assertThat(this.activeProfiles).containsExactly("my-profile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startGetsDockerComposeWithArguments() {
|
||||
this.properties.getArguments().add("--project-name=test");
|
||||
setUpRunningServices();
|
||||
this.lifecycleManager.start();
|
||||
assertThat(this.arguments).containsExactly("--project-name=test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startPublishesEvent() {
|
||||
EventCapturingListener listener = new EventCapturingListener();
|
||||
@@ -519,8 +529,10 @@ class DockerComposeLifecycleManagerTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set<String> activeProfiles) {
|
||||
protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set<String> activeProfiles,
|
||||
List<String> arguments) {
|
||||
DockerComposeLifecycleManagerTests.this.activeProfiles = activeProfiles;
|
||||
DockerComposeLifecycleManagerTests.this.arguments = arguments;
|
||||
return DockerComposeLifecycleManagerTests.this.dockerCompose;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ class DockerComposePropertiesTests {
|
||||
@Test
|
||||
void getWhenPropertiesReturnsBound() {
|
||||
Map<String, String> source = new LinkedHashMap<>();
|
||||
source.put("spring.docker.compose.arguments", "--project-name=test,--progress=auto");
|
||||
source.put("spring.docker.compose.file", "my-compose.yml");
|
||||
source.put("spring.docker.compose.lifecycle-management", "start-only");
|
||||
source.put("spring.docker.compose.host", "myhost");
|
||||
@@ -76,6 +77,7 @@ class DockerComposePropertiesTests {
|
||||
source.put("spring.docker.compose.readiness.tcp.read-timeout", "500ms");
|
||||
Binder binder = new Binder(new MapConfigurationPropertySource(source));
|
||||
DockerComposeProperties properties = DockerComposeProperties.get(binder);
|
||||
assertThat(properties.getArguments()).containsExactly("--project-name=test", "--progress=auto");
|
||||
assertThat(properties.getFile()).containsExactly(new File("my-compose.yml"));
|
||||
assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_ONLY);
|
||||
assertThat(properties.getHost()).isEqualTo("myhost");
|
||||
|
||||
Reference in New Issue
Block a user