Skip to content

Commit e2cf9e1

Browse files
committed
Respect profiles when listing running Docker Compose containers
Closes gh-40139
1 parent 68b5b95 commit e2cf9e1

File tree

4 files changed

+138
-4
lines changed

4 files changed

+138
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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.io.File;
20+
import java.io.IOException;
21+
import java.nio.charset.StandardCharsets;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.time.Duration;
25+
import java.util.List;
26+
import java.util.Set;
27+
28+
import org.assertj.core.api.Assertions;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.io.TempDir;
31+
32+
import org.springframework.boot.logging.LogLevel;
33+
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
34+
import org.springframework.boot.testsupport.container.TestImage;
35+
import org.springframework.boot.testsupport.process.DisabledIfProcessUnavailable;
36+
import org.springframework.core.io.ClassPathResource;
37+
38+
import static org.assertj.core.api.Assertions.assertThat;
39+
40+
/**
41+
* Tests for {@link DefaultDockerCompose}.
42+
*
43+
* @author Moritz Halbritter
44+
*/
45+
@DisabledIfDockerUnavailable
46+
@DisabledIfProcessUnavailable({ "docker", "compose" })
47+
class DefaultDockerComposeIntegrationTests {
48+
49+
@Test
50+
void shouldWorkWithProfiles(@TempDir Path tempDir) throws IOException {
51+
// Profile 1 contains redis1 and redis3
52+
// Profile 2 contains redis2 and redis3
53+
File composeFile = createComposeFile(tempDir, "profiles.yaml").toFile();
54+
DefaultDockerCompose dockerComposeWithProfile1 = new DefaultDockerCompose(
55+
new DockerCli(tempDir.toFile(), DockerComposeFile.of(composeFile), Set.of("1")), null);
56+
DefaultDockerCompose dockerComposeWithProfile2 = new DefaultDockerCompose(
57+
new DockerCli(tempDir.toFile(), DockerComposeFile.of(composeFile), Set.of("2")), null);
58+
DefaultDockerCompose dockerComposeWithAllProfiles = new DefaultDockerCompose(
59+
new DockerCli(tempDir.toFile(), DockerComposeFile.of(composeFile), Set.of("1", "2")), null);
60+
dockerComposeWithAllProfiles.up(LogLevel.DEBUG);
61+
try {
62+
List<RunningService> runningServicesProfile1 = dockerComposeWithProfile1.getRunningServices();
63+
assertThatContainsService(runningServicesProfile1, "redis1");
64+
assertThatDoesNotContainService(runningServicesProfile1, "redis2");
65+
assertThatContainsService(runningServicesProfile1, "redis3");
66+
67+
List<RunningService> runningServicesProfile2 = dockerComposeWithProfile2.getRunningServices();
68+
assertThatDoesNotContainService(runningServicesProfile2, "redis1");
69+
assertThatContainsService(runningServicesProfile2, "redis2");
70+
assertThatContainsService(runningServicesProfile2, "redis3");
71+
72+
// Assert that redis3 is started only once and is shared between profile 1 and
73+
// profile 2
74+
assertThat(dockerComposeWithAllProfiles.getRunningServices()).hasSize(3);
75+
RunningService redis3Profile1 = findService(runningServicesProfile1, "redis3");
76+
RunningService redis3Profile2 = findService(runningServicesProfile2, "redis3");
77+
assertThat(redis3Profile1).isNotNull();
78+
assertThat(redis3Profile2).isNotNull();
79+
assertThat(redis3Profile1.name()).isEqualTo(redis3Profile2.name());
80+
}
81+
finally {
82+
dockerComposeWithAllProfiles.down(Duration.ofSeconds(10));
83+
}
84+
}
85+
86+
private RunningService findService(List<RunningService> runningServices, String serviceName) {
87+
for (RunningService runningService : runningServices) {
88+
if (runningService.name().contains(serviceName)) {
89+
return runningService;
90+
}
91+
}
92+
return null;
93+
}
94+
95+
private void assertThatDoesNotContainService(List<RunningService> runningServices, String service) {
96+
if (findService(runningServices, service) != null) {
97+
Assertions.fail("Did not expect service '%s', but found it in [%s]", service, runningServices);
98+
}
99+
}
100+
101+
private void assertThatContainsService(List<RunningService> runningServices, String service) {
102+
if (findService(runningServices, service) == null) {
103+
Assertions.fail("Expected service '%s', but hasn't been found in [%s]", service, runningServices);
104+
}
105+
}
106+
107+
private static Path createComposeFile(Path tempDir, String resource) throws IOException {
108+
String composeFileTemplate = new ClassPathResource(resource, DockerCliIntegrationTests.class)
109+
.getContentAsString(StandardCharsets.UTF_8);
110+
String content = composeFileTemplate.replace("{imageName}", TestImage.REDIS.toString());
111+
Path composeFile = tempDir.resolve(resource);
112+
Files.writeString(composeFile, content);
113+
return composeFile;
114+
}
115+
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
services:
2+
redis1:
3+
profiles:
4+
- '1'
5+
image: '{imageName}'
6+
ports:
7+
- '6379'
8+
redis2:
9+
profiles:
10+
- '2'
11+
image: '{imageName}'
12+
ports:
13+
- '6379'
14+
redis3:
15+
image: '{imageName}'
16+
ports:
17+
- '6379'

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -151,7 +151,8 @@ static final class ComposeConfig extends DockerCliCommand<DockerCliComposeConfig
151151
static final class ComposePs extends DockerCliCommand<List<DockerCliComposePsResponse>> {
152152

153153
ComposePs() {
154-
super(Type.DOCKER_COMPOSE, DockerCliComposePsResponse.class, true, "ps", "--format=json");
154+
super(Type.DOCKER_COMPOSE, DockerCliComposePsResponse.class, true, "ps", "--orphans=false",
155+
"--format=json");
155156
}
156157

157158
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ void composeConfig() {
6262
void composePs() {
6363
DockerCliCommand<?> command = new DockerCliCommand.ComposePs();
6464
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
65-
assertThat(command.getCommand()).containsExactly("ps", "--format=json");
65+
assertThat(command.getCommand()).containsExactly("ps", "--orphans=false", "--format=json");
6666
assertThat(command.deserialize("[]")).isInstanceOf(List.class);
6767
}
6868

0 commit comments

Comments
 (0)