Skip to content

Add property to specify Docker Compose flags #42571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
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;
Expand All @@ -35,6 +37,7 @@
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeStop;
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeUp;
import org.springframework.boot.docker.compose.core.DockerCliCommand.Inspect;
import org.springframework.boot.docker.compose.core.DockerCompose.Options;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
import org.springframework.boot.testsupport.container.TestImage;
Expand All @@ -60,22 +63,26 @@ 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();
}

@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();
Options options = Options.get(DockerComposeFile.of(composeFile), Collections.emptySet(),
List.of("--project-name=" + projectName));
DockerCli cli = new DockerCli(null, options);
try {
// Verify that no services are running (this is a fresh compose project)
List<DockerCliComposePsResponse> ps = cli.run(new ComposePs());
assertThat(ps).isEmpty();
// 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
Expand Down Expand Up @@ -106,7 +113,8 @@ void runLifecycle() throws IOException {
@Test
void shouldWorkWithMultipleComposeFiles() throws IOException {
List<File> composeFiles = createComposeFiles();
DockerCli cli = new DockerCli(null, DockerComposeFile.of(composeFiles), Collections.emptySet());
Options options = Options.get(DockerComposeFile.of(composeFiles), Set.of("dev"), Collections.emptyList());
DockerCli cli = new DockerCli(null, options);
try {
// List the config and verify that both redis are there
DockerCliComposeConfigResponse config = cli.run(new ComposeConfig());
Expand Down Expand Up @@ -146,7 +154,8 @@ private static File createComposeFile(String resource) throws IOException {
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);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
services:
redis1:
profiles: [ dev ]
image: '{imageName}'
ports:
- '6379'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
redis3:
profiles: [ prod ]
image: '{imageName}'
ports:
- '6379'
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public List<RunningService> getRunningServices() {
if (runningPsResponses.isEmpty()) {
return Collections.emptyList();
}
DockerComposeFile dockerComposeFile = this.cli.getDockerComposeFile();
DockerCompose.Options options = this.cli.getDockerComposeOptions();
DockerComposeFile dockerComposeFile = options.getComposeFile();
List<RunningService> result = new ArrayList<>();
Map<String, DockerCliInspectResponse> inspected = inspect(runningPsResponses);
for (DockerCliComposePsResponse psResponse : runningPsResponses) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -31,6 +30,7 @@
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.
Expand All @@ -49,22 +49,18 @@ class DockerCli {

private final DockerCommands dockerCommands;

private final DockerComposeFile composeFile;

private final Set<String> activeProfiles;
private final DockerCompose.Options 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, DockerCompose.Options 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 : DockerCompose.Options.NONE;
}

/**
Expand Down Expand Up @@ -93,29 +89,38 @@ private List<String> createCommand(Type 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) {
for (File file : this.composeFile.getFiles()) {
DockerCompose.Options options = this.dockerComposeOptions;
DockerComposeFile composeFile = options.getComposeFile();
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 = options.getActiveProfiles();
if (!CollectionUtils.isEmpty(activeProfiles)) {
for (String profile : activeProfiles) {
result.add("--profile");
result.add(profile);
}
}
List<String> arguments = options.getArguments();
if (!CollectionUtils.isEmpty(arguments)) {
result.addAll(arguments);
}
yield result;
}
};
}

/**
* Return the {@link DockerComposeFile} being used by this CLI instance.
* @return the Docker Compose file
* Return the {@link DockerCompose.Options} being used by this CLI instance.
* @return the Docker Compose options
*/
DockerComposeFile getDockerComposeFile() {
return this.composeFile;
DockerCompose.Options getDockerComposeOptions() {
return this.dockerComposeOptions;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.docker.compose.core;

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -125,8 +126,61 @@ 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);
DockerCli cli = new DockerCli(null, Options.get(file, activeProfiles, Collections.emptyList()));
return new DefaultDockerCompose(cli, hostname);
}

/**
* Factory method used to create a {@link DockerCompose} instance.
* @param hostname the hostname used for services or {@code null} if the hostname
* @param options the Docker Compose options or {@code null}
* @return a {@link DockerCompose} instance
* @since 3.4.0
*/
static DockerCompose get(String hostname, Options options) {
DockerCli cli = new DockerCli(null, options);
return new DefaultDockerCompose(cli, hostname);
}

/**
* Docker Compose options that should be applied before any subcommand.
*/
interface Options {

/**
* No options.
*/
Options NONE = get(null, Collections.emptySet(), Collections.emptyList());

/**
* Factory method used to create a {@link DockerCompose.Options} instance.
* @param file the Docker Compose file to use
* @param activeProfiles the Docker Compose profiles to activate
* @param arguments the additional Docker Compose arguments
* @return the Docker Compose options
*/
static Options get(DockerComposeFile file, Set<String> activeProfiles, List<String> arguments) {
return new DockerComposeOptions(file, activeProfiles, arguments);
}

/**
* the Docker Compose a file to use.
* @return compose a file to use
*/
DockerComposeFile getComposeFile();

/**
* the Docker Compose profiles to activate.
* @return profiles to activate
*/
Set<String> getActiveProfiles();

/**
* the additional Docker Compose arguments.
* @return additional arguments
*/
List<String> getArguments();

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.docker.compose.core;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.springframework.core.style.ToStringCreator;

/**
* Default {@link DockerCompose.Options} implementation.
*
* @author Dmytro Nosan
*/
final class DockerComposeOptions implements DockerCompose.Options {

private final DockerComposeFile composeFile;

private final Set<String> activeProfiles;

private final List<String> arguments;

/**
* Create a new {@link DockerComposeOptions} instance.
* @param composeFile the Docker Compose file to use
* @param activeProfiles the Docker Compose profiles to activate
* @param arguments the additional Docker Compose arguments (e.g. --project-name=...)
*/
DockerComposeOptions(DockerComposeFile composeFile, Set<String> activeProfiles, List<String> arguments) {
this.composeFile = composeFile;
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
this.arguments = (arguments != null) ? arguments : Collections.emptyList();
}

@Override
public DockerComposeFile getComposeFile() {
return this.composeFile;
}

@Override
public Set<String> getActiveProfiles() {
return this.activeProfiles;
}

@Override
public List<String> getArguments() {
return this.arguments;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
DockerComposeOptions that = (DockerComposeOptions) obj;
return Objects.equals(this.composeFile, that.composeFile)
&& Objects.equals(this.activeProfiles, that.activeProfiles)
&& Objects.equals(this.arguments, that.arguments);
}

@Override
public int hashCode() {
return Objects.hash(this.composeFile, this.activeProfiles, this.arguments);
}

@Override
public String toString() {
ToStringCreator creator = new ToStringCreator(this);
creator.append("composeFile", this.composeFile);
creator.append("activeProfiles", this.activeProfiles);
creator.append("arguments", this.arguments);
return creator.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ void start() {
}
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));
Expand Down Expand Up @@ -159,8 +160,10 @@ protected DockerComposeFile getComposeFile() {
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) {
DockerCompose.Options options = DockerCompose.Options.get(composeFile, activeProfiles, arguments);
return DockerCompose.get(this.properties.getHost(), options);
}

private boolean isIgnored(RunningService service) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -88,6 +93,10 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public List<String> getArguments() {
return this.arguments;
}

public List<File> getFile() {
return this.file;
}
Expand Down
Loading