Skip to content

Commit d998720

Browse files
committed
Merge branch '3.3.x' into 3.4.x
Closes gh-43717
2 parents b631b06 + 9625146 commit d998720

File tree

3 files changed

+105
-26
lines changed

3 files changed

+105
-26
lines changed

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

+21-11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.commons.logging.Log;
2929
import org.apache.commons.logging.LogFactory;
3030

31+
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion;
3132
import org.springframework.boot.docker.compose.core.DockerCliCommand.Type;
3233
import org.springframework.boot.logging.LogLevel;
3334
import org.springframework.core.log.LogMessage;
@@ -52,6 +53,8 @@ class DockerCli {
5253

5354
private final DockerComposeOptions dockerComposeOptions;
5455

56+
private final ComposeVersion composeVersion;
57+
5558
/**
5659
* Create a new {@link DockerCli} instance.
5760
* @param workingDirectory the working directory or {@code null}
@@ -62,6 +65,7 @@ class DockerCli {
6265
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
6366
(key) -> new DockerCommands(this.processRunner));
6467
this.dockerComposeOptions = (dockerComposeOptions != null) ? dockerComposeOptions : DockerComposeOptions.none();
68+
this.composeVersion = ComposeVersion.of(this.dockerCommands.get(Type.DOCKER_COMPOSE).version());
6569
}
6670

6771
/**
@@ -72,7 +76,7 @@ class DockerCli {
7276
*/
7377
<R> R run(DockerCliCommand<R> dockerCommand) {
7478
List<String> command = createCommand(dockerCommand.getType());
75-
command.addAll(dockerCommand.getCommand());
79+
command.addAll(dockerCommand.getCommand(this.composeVersion));
7680
Consumer<String> outputConsumer = createOutputConsumer(dockerCommand.getLogLevel());
7781
String json = this.processRunner.run(outputConsumer, command.toArray(new String[0]));
7882
return dockerCommand.deserialize(json);
@@ -87,9 +91,9 @@ private Consumer<String> createOutputConsumer(LogLevel logLevel) {
8791

8892
private List<String> createCommand(Type type) {
8993
return switch (type) {
90-
case DOCKER -> new ArrayList<>(this.dockerCommands.get(type));
94+
case DOCKER -> new ArrayList<>(this.dockerCommands.get(type).command());
9195
case DOCKER_COMPOSE -> {
92-
List<String> result = new ArrayList<>(this.dockerCommands.get(type));
96+
List<String> result = new ArrayList<>(this.dockerCommands.get(type).command());
9397
DockerComposeFile composeFile = this.dockerComposeOptions.composeFile();
9498
if (composeFile != null) {
9599
for (File file : composeFile.getFiles()) {
@@ -128,20 +132,20 @@ DockerComposeFile getDockerComposeFile() {
128132
*/
129133
private static class DockerCommands {
130134

131-
private final List<String> dockerCommand;
135+
private final DockerCommand dockerCommand;
132136

133-
private final List<String> dockerComposeCommand;
137+
private final DockerCommand dockerComposeCommand;
134138

135139
DockerCommands(ProcessRunner processRunner) {
136140
this.dockerCommand = getDockerCommand(processRunner);
137141
this.dockerComposeCommand = getDockerComposeCommand(processRunner);
138142
}
139143

140-
private List<String> getDockerCommand(ProcessRunner processRunner) {
144+
private DockerCommand getDockerCommand(ProcessRunner processRunner) {
141145
try {
142146
String version = processRunner.run("docker", "version", "--format", "{{.Client.Version}}");
143147
logger.trace(LogMessage.format("Using docker %s", version));
144-
return List.of("docker");
148+
return new DockerCommand(version, List.of("docker"));
145149
}
146150
catch (ProcessStartException ex) {
147151
throw new DockerProcessStartException("Unable to start docker process. Is docker correctly installed?",
@@ -156,13 +160,13 @@ private List<String> getDockerCommand(ProcessRunner processRunner) {
156160
}
157161
}
158162

159-
private List<String> getDockerComposeCommand(ProcessRunner processRunner) {
163+
private DockerCommand getDockerComposeCommand(ProcessRunner processRunner) {
160164
try {
161165
DockerCliComposeVersionResponse response = DockerJson.deserialize(
162166
processRunner.run("docker", "compose", "version", "--format", "json"),
163167
DockerCliComposeVersionResponse.class);
164168
logger.trace(LogMessage.format("Using Docker Compose %s", response.version()));
165-
return List.of("docker", "compose");
169+
return new DockerCommand(response.version(), List.of("docker", "compose"));
166170
}
167171
catch (ProcessExitException ex) {
168172
// Ignore and try docker-compose
@@ -172,7 +176,7 @@ private List<String> getDockerComposeCommand(ProcessRunner processRunner) {
172176
processRunner.run("docker-compose", "version", "--format", "json"),
173177
DockerCliComposeVersionResponse.class);
174178
logger.trace(LogMessage.format("Using docker-compose %s", response.version()));
175-
return List.of("docker-compose");
179+
return new DockerCommand(response.version(), List.of("docker-compose"));
176180
}
177181
catch (ProcessStartException ex) {
178182
throw new DockerProcessStartException(
@@ -181,7 +185,7 @@ private List<String> getDockerComposeCommand(ProcessRunner processRunner) {
181185
}
182186
}
183187

184-
List<String> get(Type type) {
188+
DockerCommand get(Type type) {
185189
return switch (type) {
186190
case DOCKER -> this.dockerCommand;
187191
case DOCKER_COMPOSE -> this.dockerComposeCommand;
@@ -190,6 +194,10 @@ List<String> get(Type type) {
190194

191195
}
192196

197+
private record DockerCommand(String version, List<String> command) {
198+
199+
}
200+
193201
/**
194202
* Options for Docker Compose.
195203
*
@@ -198,6 +206,7 @@ List<String> get(Type type) {
198206
* @param arguments the arguments to pass to Docker Compose
199207
*/
200208
record DockerComposeOptions(DockerComposeFile composeFile, Set<String> activeProfiles, List<String> arguments) {
209+
201210
DockerComposeOptions {
202211
activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
203212
arguments = (arguments != null) ? arguments : Collections.emptyList();
@@ -206,6 +215,7 @@ record DockerComposeOptions(DockerComposeFile composeFile, Set<String> activePro
206215
static DockerComposeOptions none() {
207216
return new DockerComposeOptions(null, null, null);
208217
}
218+
209219
}
210220

211221
}

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

+49-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.util.ArrayList;
2121
import java.util.Collection;
2222
import java.util.List;
23+
import java.util.Locale;
2324
import java.util.Objects;
25+
import java.util.function.Function;
2426

2527
import org.springframework.boot.logging.LogLevel;
2628

@@ -42,19 +44,24 @@ abstract sealed class DockerCliCommand<R> {
4244

4345
private final boolean listResponse;
4446

45-
private final List<String> command;
47+
private final Function<ComposeVersion, List<String>> command;
4648

4749
private DockerCliCommand(Type type, Class<?> responseType, boolean listResponse, String... command) {
4850
this(type, LogLevel.OFF, responseType, listResponse, command);
4951
}
5052

5153
private DockerCliCommand(Type type, LogLevel logLevel, Class<?> responseType, boolean listResponse,
5254
String... command) {
55+
this(type, logLevel, responseType, listResponse, (version) -> List.of(command));
56+
}
57+
58+
private DockerCliCommand(Type type, LogLevel logLevel, Class<?> responseType, boolean listResponse,
59+
Function<ComposeVersion, List<String>> command) {
5360
this.type = type;
5461
this.logLevel = logLevel;
5562
this.responseType = responseType;
5663
this.listResponse = listResponse;
57-
this.command = List.of(command);
64+
this.command = command;
5865
}
5966

6067
Type getType() {
@@ -65,8 +72,8 @@ LogLevel getLogLevel() {
6572
return this.logLevel;
6673
}
6774

68-
List<String> getCommand() {
69-
return this.command;
75+
List<String> getCommand(ComposeVersion composeVersion) {
76+
return this.command.apply(composeVersion);
7077
}
7178

7279
@SuppressWarnings("unchecked")
@@ -90,7 +97,8 @@ public boolean equals(Object obj) {
9097
boolean result = this.type == other.type;
9198
result = result && this.responseType == other.responseType;
9299
result = result && this.listResponse == other.listResponse;
93-
result = result && this.command.equals(other.command);
100+
result = result
101+
&& this.command.apply(ComposeVersion.UNKNOWN).equals(other.command.apply(ComposeVersion.UNKNOWN));
94102
return result;
95103
}
96104

@@ -150,9 +158,16 @@ static final class ComposeConfig extends DockerCliCommand<DockerCliComposeConfig
150158
*/
151159
static final class ComposePs extends DockerCliCommand<List<DockerCliComposePsResponse>> {
152160

161+
private static final List<String> WITHOUT_ORPHANS = List.of("ps", "--format=json");
162+
163+
private static final List<String> WITH_ORPHANS = List.of("ps", "--orphans=false", "--format=json");
164+
153165
ComposePs() {
154-
super(Type.DOCKER_COMPOSE, DockerCliComposePsResponse.class, true, "ps", "--orphans=false",
155-
"--format=json");
166+
super(Type.DOCKER_COMPOSE, LogLevel.OFF, DockerCliComposePsResponse.class, true, ComposePs::getPsCommand);
167+
}
168+
169+
private static List<String> getPsCommand(ComposeVersion composeVersion) {
170+
return (composeVersion.isLessThan(2, 24)) ? WITHOUT_ORPHANS : WITH_ORPHANS;
156171
}
157172

158173
}
@@ -253,4 +268,31 @@ enum Type {
253268

254269
}
255270

271+
/**
272+
* Docker compose version.
273+
*
274+
* @param major the major component
275+
* @param minor the minor component
276+
*/
277+
record ComposeVersion(int major, int minor) {
278+
279+
public static final ComposeVersion UNKNOWN = new ComposeVersion(0, 0);
280+
281+
boolean isLessThan(int major, int minor) {
282+
return major() < major || major() == major && minor() < minor;
283+
}
284+
285+
static ComposeVersion of(String value) {
286+
try {
287+
value = (!value.toLowerCase(Locale.ROOT).startsWith("v")) ? value : value.substring(1);
288+
String[] parts = value.split("\\.");
289+
return new ComposeVersion(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
290+
}
291+
catch (Exception ex) {
292+
return UNKNOWN;
293+
}
294+
}
295+
296+
}
297+
256298
}

spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliCommandTests.java

+35-8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.junit.jupiter.api.Test;
2323

24+
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion;
2425
import org.springframework.boot.logging.LogLevel;
2526

2627
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,35 +35,46 @@
3435
*/
3536
class DockerCliCommandTests {
3637

38+
private static final ComposeVersion COMPOSE_VERSION = ComposeVersion.of("2.31.0");
39+
3740
@Test
3841
void context() {
3942
DockerCliCommand<?> command = new DockerCliCommand.Context();
4043
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER);
41-
assertThat(command.getCommand()).containsExactly("context", "ls", "--format={{ json . }}");
44+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("context", "ls", "--format={{ json . }}");
4245
assertThat(command.deserialize("[]")).isInstanceOf(List.class);
4346
}
4447

4548
@Test
4649
void inspect() {
4750
DockerCliCommand<?> command = new DockerCliCommand.Inspect(List.of("123", "345"));
4851
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER);
49-
assertThat(command.getCommand()).containsExactly("inspect", "--format={{ json . }}", "123", "345");
52+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("inspect", "--format={{ json . }}", "123",
53+
"345");
5054
assertThat(command.deserialize("[]")).isInstanceOf(List.class);
5155
}
5256

5357
@Test
5458
void composeConfig() {
5559
DockerCliCommand<?> command = new DockerCliCommand.ComposeConfig();
5660
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
57-
assertThat(command.getCommand()).containsExactly("config", "--format=json");
61+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("config", "--format=json");
5862
assertThat(command.deserialize("{}")).isInstanceOf(DockerCliComposeConfigResponse.class);
5963
}
6064

6165
@Test
6266
void composePs() {
6367
DockerCliCommand<?> command = new DockerCliCommand.ComposePs();
6468
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
65-
assertThat(command.getCommand()).containsExactly("ps", "--orphans=false", "--format=json");
69+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("ps", "--orphans=false", "--format=json");
70+
assertThat(command.deserialize("[]")).isInstanceOf(List.class);
71+
}
72+
73+
@Test
74+
void composePsWhenLessThanV224() {
75+
DockerCliCommand<?> command = new DockerCliCommand.ComposePs();
76+
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
77+
assertThat(command.getCommand(ComposeVersion.of("2.23"))).containsExactly("ps", "--format=json");
6678
assertThat(command.deserialize("[]")).isInstanceOf(List.class);
6779
}
6880

@@ -71,7 +83,7 @@ void composeUp() {
7183
DockerCliCommand<?> command = new DockerCliCommand.ComposeUp(LogLevel.INFO, List.of("--renew-anon-volumes"));
7284
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
7385
assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO);
74-
assertThat(command.getCommand()).containsExactly("up", "--no-color", "--detach", "--wait",
86+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("up", "--no-color", "--detach", "--wait",
7587
"--renew-anon-volumes");
7688
assertThat(command.deserialize("[]")).isNull();
7789
}
@@ -81,7 +93,7 @@ void composeDown() {
8193
DockerCliCommand<?> command = new DockerCliCommand.ComposeDown(Duration.ofSeconds(1),
8294
List.of("--remove-orphans"));
8395
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
84-
assertThat(command.getCommand()).containsExactly("down", "--timeout", "1", "--remove-orphans");
96+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("down", "--timeout", "1", "--remove-orphans");
8597
assertThat(command.deserialize("[]")).isNull();
8698
}
8799

@@ -90,16 +102,31 @@ void composeStart() {
90102
DockerCliCommand<?> command = new DockerCliCommand.ComposeStart(LogLevel.INFO, List.of("--dry-run"));
91103
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
92104
assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO);
93-
assertThat(command.getCommand()).containsExactly("start", "--dry-run");
105+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("start", "--dry-run");
94106
assertThat(command.deserialize("[]")).isNull();
95107
}
96108

97109
@Test
98110
void composeStop() {
99111
DockerCliCommand<?> command = new DockerCliCommand.ComposeStop(Duration.ofSeconds(1), List.of("--dry-run"));
100112
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
101-
assertThat(command.getCommand()).containsExactly("stop", "--timeout", "1", "--dry-run");
113+
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("stop", "--timeout", "1", "--dry-run");
102114
assertThat(command.deserialize("[]")).isNull();
103115
}
104116

117+
@Test
118+
void composeVersionTests() {
119+
ComposeVersion version = ComposeVersion.of("2.31.0-desktop");
120+
assertThat(version.major()).isEqualTo(2);
121+
assertThat(version.minor()).isEqualTo(31);
122+
assertThat(version.isLessThan(1, 0)).isFalse();
123+
assertThat(version.isLessThan(2, 0)).isFalse();
124+
assertThat(version.isLessThan(2, 31)).isFalse();
125+
assertThat(version.isLessThan(2, 32)).isTrue();
126+
assertThat(version.isLessThan(3, 0)).isTrue();
127+
ComposeVersion versionWithPrefix = ComposeVersion.of("v2.31.0-desktop");
128+
assertThat(versionWithPrefix.major()).isEqualTo(2);
129+
assertThat(versionWithPrefix.minor()).isEqualTo(31);
130+
}
131+
105132
}

0 commit comments

Comments
 (0)