Skip to content

Commit 2bee29c

Browse files
nosanmhalbritter
authored andcommitted
Add property to specify Docker Compose flags
See gh-42571
1 parent 31fada6 commit 2bee29c

File tree

12 files changed

+227
-28
lines changed

12 files changed

+227
-28
lines changed

spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.time.Duration;
2525
import java.util.Collections;
2626
import java.util.List;
27+
import java.util.Set;
28+
import java.util.UUID;
2729

2830
import org.junit.jupiter.api.Test;
2931
import org.junit.jupiter.api.io.TempDir;
@@ -35,6 +37,7 @@
3537
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeStop;
3638
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeUp;
3739
import org.springframework.boot.docker.compose.core.DockerCliCommand.Inspect;
40+
import org.springframework.boot.docker.compose.core.DockerCompose.Options;
3841
import org.springframework.boot.logging.LogLevel;
3942
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
4043
import org.springframework.boot.testsupport.container.TestImage;
@@ -60,22 +63,26 @@ class DockerCliIntegrationTests {
6063

6164
@Test
6265
void runBasicCommand() {
63-
DockerCli cli = new DockerCli(null, null, Collections.emptySet());
66+
DockerCli cli = new DockerCli(null, null);
6467
List<DockerCliContextResponse> context = cli.run(new DockerCliCommand.Context());
6568
assertThat(context).isNotEmpty();
6669
}
6770

6871
@Test
6972
void runLifecycle() throws IOException {
7073
File composeFile = createComposeFile("redis-compose.yaml");
71-
DockerCli cli = new DockerCli(null, DockerComposeFile.of(composeFile), Collections.emptySet());
74+
String projectName = UUID.randomUUID().toString();
75+
Options options = Options.get(DockerComposeFile.of(composeFile), Collections.emptySet(),
76+
List.of("--project-name=" + projectName));
77+
DockerCli cli = new DockerCli(null, options);
7278
try {
7379
// Verify that no services are running (this is a fresh compose project)
7480
List<DockerCliComposePsResponse> ps = cli.run(new ComposePs());
7581
assertThat(ps).isEmpty();
7682
// List the config and verify that redis is there
7783
DockerCliComposeConfigResponse config = cli.run(new ComposeConfig());
7884
assertThat(config.services()).containsOnlyKeys("redis");
85+
assertThat(config.name()).isEqualTo(projectName);
7986
// Run up
8087
cli.run(new ComposeUp(LogLevel.INFO, Collections.emptyList()));
8188
// Run ps and use id to run inspect on the id
@@ -106,7 +113,8 @@ void runLifecycle() throws IOException {
106113
@Test
107114
void shouldWorkWithMultipleComposeFiles() throws IOException {
108115
List<File> composeFiles = createComposeFiles();
109-
DockerCli cli = new DockerCli(null, DockerComposeFile.of(composeFiles), Collections.emptySet());
116+
Options options = Options.get(DockerComposeFile.of(composeFiles), Set.of("dev"), Collections.emptyList());
117+
DockerCli cli = new DockerCli(null, options);
110118
try {
111119
// List the config and verify that both redis are there
112120
DockerCliComposeConfigResponse config = cli.run(new ComposeConfig());
@@ -146,7 +154,8 @@ private static File createComposeFile(String resource) throws IOException {
146154
private static List<File> createComposeFiles() throws IOException {
147155
File file1 = createComposeFile("1.yaml");
148156
File file2 = createComposeFile("2.yaml");
149-
return List.of(file1, file2);
157+
File file3 = createComposeFile("3.yaml");
158+
return List.of(file1, file2, file3);
150159
}
151160

152161
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
services:
22
redis1:
3+
profiles: [ dev ]
34
image: '{imageName}'
45
ports:
56
- '6379'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
redis3:
3+
profiles: [ prod ]
4+
image: '{imageName}'
5+
ports:
6+
- '6379'

spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ public List<RunningService> getRunningServices() {
9797
if (runningPsResponses.isEmpty()) {
9898
return Collections.emptyList();
9999
}
100-
DockerComposeFile dockerComposeFile = this.cli.getDockerComposeFile();
100+
DockerCompose.Options options = this.cli.getDockerComposeOptions();
101+
DockerComposeFile dockerComposeFile = options.getComposeFile();
101102
List<RunningService> result = new ArrayList<>();
102103
Map<String, DockerCliInspectResponse> inspected = inspect(runningPsResponses);
103104
for (DockerCliComposePsResponse psResponse : runningPsResponses) {

spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.io.File;
2020
import java.util.ArrayList;
21-
import java.util.Collections;
2221
import java.util.HashMap;
2322
import java.util.List;
2423
import java.util.Map;
@@ -31,6 +30,7 @@
3130
import org.springframework.boot.docker.compose.core.DockerCliCommand.Type;
3231
import org.springframework.boot.logging.LogLevel;
3332
import org.springframework.core.log.LogMessage;
33+
import org.springframework.util.CollectionUtils;
3434

3535
/**
3636
* Wrapper around {@code docker} and {@code docker-compose} command line tools.
@@ -49,22 +49,18 @@ class DockerCli {
4949

5050
private final DockerCommands dockerCommands;
5151

52-
private final DockerComposeFile composeFile;
53-
54-
private final Set<String> activeProfiles;
52+
private final DockerCompose.Options dockerComposeOptions;
5553

5654
/**
5755
* Create a new {@link DockerCli} instance.
5856
* @param workingDirectory the working directory or {@code null}
59-
* @param composeFile the Docker Compose file to use
60-
* @param activeProfiles the Docker Compose profiles to activate
57+
* @param dockerComposeOptions the Docker Compose options to use or {@code null}.
6158
*/
62-
DockerCli(File workingDirectory, DockerComposeFile composeFile, Set<String> activeProfiles) {
59+
DockerCli(File workingDirectory, DockerCompose.Options dockerComposeOptions) {
6360
this.processRunner = new ProcessRunner(workingDirectory);
6461
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
6562
(key) -> new DockerCommands(this.processRunner));
66-
this.composeFile = composeFile;
67-
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
63+
this.dockerComposeOptions = (dockerComposeOptions != null) ? dockerComposeOptions : DockerCompose.Options.NONE;
6864
}
6965

7066
/**
@@ -93,29 +89,38 @@ private List<String> createCommand(Type type) {
9389
case DOCKER -> new ArrayList<>(this.dockerCommands.get(type));
9490
case DOCKER_COMPOSE -> {
9591
List<String> result = new ArrayList<>(this.dockerCommands.get(type));
96-
if (this.composeFile != null) {
97-
for (File file : this.composeFile.getFiles()) {
92+
DockerCompose.Options options = this.dockerComposeOptions;
93+
DockerComposeFile composeFile = options.getComposeFile();
94+
if (composeFile != null) {
95+
for (File file : composeFile.getFiles()) {
9896
result.add("--file");
9997
result.add(file.getPath());
10098
}
10199
}
102100
result.add("--ansi");
103101
result.add("never");
104-
for (String profile : this.activeProfiles) {
105-
result.add("--profile");
106-
result.add(profile);
102+
Set<String> activeProfiles = options.getActiveProfiles();
103+
if (!CollectionUtils.isEmpty(activeProfiles)) {
104+
for (String profile : activeProfiles) {
105+
result.add("--profile");
106+
result.add(profile);
107+
}
108+
}
109+
List<String> arguments = options.getArguments();
110+
if (!CollectionUtils.isEmpty(arguments)) {
111+
result.addAll(arguments);
107112
}
108113
yield result;
109114
}
110115
};
111116
}
112117

113118
/**
114-
* Return the {@link DockerComposeFile} being used by this CLI instance.
115-
* @return the Docker Compose file
119+
* Return the {@link DockerCompose.Options} being used by this CLI instance.
120+
* @return the Docker Compose options
116121
*/
117-
DockerComposeFile getDockerComposeFile() {
118-
return this.composeFile;
122+
DockerCompose.Options getDockerComposeOptions() {
123+
return this.dockerComposeOptions;
119124
}
120125

121126
/**

spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.docker.compose.core;
1818

1919
import java.time.Duration;
20+
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Set;
2223

@@ -125,8 +126,61 @@ public interface DockerCompose {
125126
* @return a {@link DockerCompose} instance
126127
*/
127128
static DockerCompose get(DockerComposeFile file, String hostname, Set<String> activeProfiles) {
128-
DockerCli cli = new DockerCli(null, file, activeProfiles);
129+
DockerCli cli = new DockerCli(null, Options.get(file, activeProfiles, Collections.emptyList()));
129130
return new DefaultDockerCompose(cli, hostname);
130131
}
131132

133+
/**
134+
* Factory method used to create a {@link DockerCompose} instance.
135+
* @param hostname the hostname used for services or {@code null} if the hostname
136+
* @param options the Docker Compose options or {@code null}
137+
* @return a {@link DockerCompose} instance
138+
* @since 3.4.0
139+
*/
140+
static DockerCompose get(String hostname, Options options) {
141+
DockerCli cli = new DockerCli(null, options);
142+
return new DefaultDockerCompose(cli, hostname);
143+
}
144+
145+
/**
146+
* Docker Compose options that should be applied before any subcommand.
147+
*/
148+
interface Options {
149+
150+
/**
151+
* No options.
152+
*/
153+
Options NONE = get(null, Collections.emptySet(), Collections.emptyList());
154+
155+
/**
156+
* Factory method used to create a {@link DockerCompose.Options} instance.
157+
* @param file the Docker Compose file to use
158+
* @param activeProfiles the Docker Compose profiles to activate
159+
* @param arguments the additional Docker Compose arguments
160+
* @return the Docker Compose options
161+
*/
162+
static Options get(DockerComposeFile file, Set<String> activeProfiles, List<String> arguments) {
163+
return new DockerComposeOptions(file, activeProfiles, arguments);
164+
}
165+
166+
/**
167+
* the Docker Compose a file to use.
168+
* @return compose a file to use
169+
*/
170+
DockerComposeFile getComposeFile();
171+
172+
/**
173+
* the Docker Compose profiles to activate.
174+
* @return profiles to activate
175+
*/
176+
Set<String> getActiveProfiles();
177+
178+
/**
179+
* the additional Docker Compose arguments.
180+
* @return additional arguments
181+
*/
182+
List<String> getArguments();
183+
184+
}
185+
132186
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.core;
18+
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Objects;
22+
import java.util.Set;
23+
24+
import org.springframework.core.style.ToStringCreator;
25+
26+
/**
27+
* Default {@link DockerCompose.Options} implementation.
28+
*
29+
* @author Dmytro Nosan
30+
*/
31+
final class DockerComposeOptions implements DockerCompose.Options {
32+
33+
private final DockerComposeFile composeFile;
34+
35+
private final Set<String> activeProfiles;
36+
37+
private final List<String> arguments;
38+
39+
/**
40+
* Create a new {@link DockerComposeOptions} instance.
41+
* @param composeFile the Docker Compose file to use
42+
* @param activeProfiles the Docker Compose profiles to activate
43+
* @param arguments the additional Docker Compose arguments (e.g. --project-name=...)
44+
*/
45+
DockerComposeOptions(DockerComposeFile composeFile, Set<String> activeProfiles, List<String> arguments) {
46+
this.composeFile = composeFile;
47+
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
48+
this.arguments = (arguments != null) ? arguments : Collections.emptyList();
49+
}
50+
51+
@Override
52+
public DockerComposeFile getComposeFile() {
53+
return this.composeFile;
54+
}
55+
56+
@Override
57+
public Set<String> getActiveProfiles() {
58+
return this.activeProfiles;
59+
}
60+
61+
@Override
62+
public List<String> getArguments() {
63+
return this.arguments;
64+
}
65+
66+
@Override
67+
public boolean equals(Object obj) {
68+
if (this == obj) {
69+
return true;
70+
}
71+
if (obj == null || getClass() != obj.getClass()) {
72+
return false;
73+
}
74+
DockerComposeOptions that = (DockerComposeOptions) obj;
75+
return Objects.equals(this.composeFile, that.composeFile)
76+
&& Objects.equals(this.activeProfiles, that.activeProfiles)
77+
&& Objects.equals(this.arguments, that.arguments);
78+
}
79+
80+
@Override
81+
public int hashCode() {
82+
return Objects.hash(this.composeFile, this.activeProfiles, this.arguments);
83+
}
84+
85+
@Override
86+
public String toString() {
87+
ToStringCreator creator = new ToStringCreator(this);
88+
creator.append("composeFile", this.composeFile);
89+
creator.append("activeProfiles", this.activeProfiles);
90+
creator.append("arguments", this.arguments);
91+
return creator.toString();
92+
}
93+
94+
}

spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ void start() {
109109
}
110110
DockerComposeFile composeFile = getComposeFile();
111111
Set<String> activeProfiles = this.properties.getProfiles().getActive();
112-
DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles);
112+
List<String> arguments = this.properties.getArguments();
113+
DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles, arguments);
113114
if (!dockerCompose.hasDefinedServices()) {
114115
logger.warn(LogMessage.format("No services defined in Docker Compose file %s with active profiles %s",
115116
composeFile, activeProfiles));
@@ -159,8 +160,10 @@ protected DockerComposeFile getComposeFile() {
159160
return composeFile;
160161
}
161162

162-
protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set<String> activeProfiles) {
163-
return DockerCompose.get(composeFile, this.properties.getHost(), activeProfiles);
163+
protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set<String> activeProfiles,
164+
List<String> arguments) {
165+
DockerCompose.Options options = DockerCompose.Options.get(composeFile, activeProfiles, arguments);
166+
return DockerCompose.get(this.properties.getHost(), options);
164167
}
165168

166169
private boolean isIgnored(RunningService service) {

spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public class DockerComposeProperties {
4646
*/
4747
private boolean enabled = true;
4848

49+
/**
50+
* Arguments to pass to the docker compose command.
51+
*/
52+
private final List<String> arguments = new ArrayList<>();
53+
4954
/**
5055
* Paths to the Docker Compose configuration files.
5156
*/
@@ -88,6 +93,10 @@ public void setEnabled(boolean enabled) {
8893
this.enabled = enabled;
8994
}
9095

96+
public List<String> getArguments() {
97+
return this.arguments;
98+
}
99+
91100
public List<File> getFile() {
92101
return this.file;
93102
}

0 commit comments

Comments
 (0)