Skip to content

Commit e2941dc

Browse files
Add support to read up to 1000 service aliases in compose file (#8816)
1 parent 62a383b commit e2941dc

File tree

2 files changed

+52
-10
lines changed

2 files changed

+52
-10
lines changed

core/src/main/java/org/testcontainers/containers/ParsedDockerComposeFile.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
import lombok.extern.slf4j.Slf4j;
88
import org.apache.commons.io.FileUtils;
99
import org.testcontainers.images.ParsedDockerfile;
10+
import org.yaml.snakeyaml.DumperOptions;
1011
import org.yaml.snakeyaml.LoaderOptions;
1112
import org.yaml.snakeyaml.Yaml;
1213
import org.yaml.snakeyaml.constructor.SafeConstructor;
14+
import org.yaml.snakeyaml.representer.Representer;
15+
import org.yaml.snakeyaml.resolver.Resolver;
1316

1417
import java.io.File;
1518
import java.io.FileInputStream;
@@ -34,10 +37,20 @@ class ParsedDockerComposeFile {
3437
private final File composeFile;
3538

3639
@Getter
37-
private Map<String, Set<String>> serviceNameToImageNames = new HashMap<>();
40+
private final Map<String, Set<String>> serviceNameToImageNames = new HashMap<>();
3841

3942
ParsedDockerComposeFile(File composeFile) {
40-
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
43+
// The default is 50 and a big docker-compose.yml file can easily go above that number. 1,000 should give us some room
44+
LoaderOptions options = new LoaderOptions();
45+
options.setMaxAliasesForCollections(1_000);
46+
DumperOptions dumperOptions = new DumperOptions();
47+
Yaml yaml = new Yaml(
48+
new SafeConstructor(options),
49+
new Representer(dumperOptions),
50+
dumperOptions,
51+
options,
52+
new Resolver()
53+
);
4154
try (FileInputStream fileInputStream = FileUtils.openInputStream(composeFile)) {
4255
composeFileContent = yaml.load(fileInputStream);
4356
} catch (Exception e) {
@@ -82,7 +95,9 @@ private void parseAndValidate() {
8295
return;
8396
}
8497

85-
servicesMap = (Map<String, ?>) servicesElement;
98+
@SuppressWarnings("unchecked")
99+
Map<String, ?> temp = (Map<String, ?>) servicesElement;
100+
servicesMap = temp;
86101
} else {
87102
servicesMap = composeFileContent;
88103
}
@@ -99,15 +114,16 @@ private void parseAndValidate() {
99114
break;
100115
}
101116

102-
final Map serviceDefinitionMap = (Map) serviceDefinition;
117+
@SuppressWarnings("unchecked")
118+
final Map<String, ?> serviceDefinitionMap = (Map<String, ?>) serviceDefinition;
103119

104120
validateNoContainerNameSpecified(serviceName, serviceDefinitionMap);
105121
findServiceImageName(serviceName, serviceDefinitionMap);
106122
findImageNamesInDockerfile(serviceName, serviceDefinitionMap);
107123
}
108124
}
109125

110-
private void validateNoContainerNameSpecified(String serviceName, Map serviceDefinitionMap) {
126+
private void validateNoContainerNameSpecified(String serviceName, Map<String, ?> serviceDefinitionMap) {
111127
if (serviceDefinitionMap.containsKey("container_name")) {
112128
throw new IllegalStateException(
113129
String.format(
@@ -119,20 +135,21 @@ private void validateNoContainerNameSpecified(String serviceName, Map serviceDef
119135
}
120136
}
121137

122-
private void findServiceImageName(String serviceName, Map serviceDefinitionMap) {
123-
if (serviceDefinitionMap.containsKey("image") && serviceDefinitionMap.get("image") instanceof String) {
124-
final String imageName = (String) serviceDefinitionMap.get("image");
138+
private void findServiceImageName(String serviceName, Map<String, ?> serviceDefinitionMap) {
139+
Object result = serviceDefinitionMap.get("image");
140+
if (result instanceof String) {
141+
final String imageName = (String) result;
125142
log.debug("Resolved dependency image for Docker Compose in {}: {}", composeFileName, imageName);
126143
serviceNameToImageNames.put(serviceName, Sets.newHashSet(imageName));
127144
}
128145
}
129146

130-
private void findImageNamesInDockerfile(String serviceName, Map serviceDefinitionMap) {
147+
private void findImageNamesInDockerfile(String serviceName, Map<String, ?> serviceDefinitionMap) {
131148
final Object buildNode = serviceDefinitionMap.get("build");
132149
Path dockerfilePath = null;
133150

134151
if (buildNode instanceof Map) {
135-
final Map buildElement = (Map) buildNode;
152+
final Map<?, ?> buildElement = (Map<?, ?>) buildNode;
136153
final Object dockerfileRelativePath = buildElement.get("dockerfile");
137154
final Object contextRelativePath = buildElement.get("context");
138155
if (dockerfileRelativePath instanceof String && contextRelativePath instanceof String) {

core/src/test/java/org/testcontainers/containers/ParsedDockerComposeFileValidationTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33
import com.google.common.collect.ImmutableMap;
44
import com.google.common.collect.Sets;
55
import lombok.SneakyThrows;
6+
import org.junit.Rule;
67
import org.junit.Test;
8+
import org.junit.rules.TemporaryFolder;
79

810
import java.io.File;
11+
import java.io.PrintWriter;
912
import java.util.Collections;
1013

1114
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.assertj.core.api.Assertions.assertThatNoException;
1216
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1317
import static org.assertj.core.api.Assertions.entry;
1418

1519
public class ParsedDockerComposeFileValidationTest {
1620

21+
@Rule
22+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
23+
1724
@Test
1825
public void shouldValidate() {
1926
File file = new File("src/test/resources/docker-compose-container-name-v1.yml");
@@ -129,4 +136,22 @@ public void shouldObtainImageFromDockerfileBuildWithContext() {
129136
entry("custom", Sets.newHashSet("alpine:3.17"))
130137
); // redis, mysql from compose file, alpine:3.17 from Dockerfile build
131138
}
139+
140+
@Test
141+
public void shouldSupportALotOfAliases() throws Exception {
142+
File file = temporaryFolder.newFile();
143+
try (PrintWriter writer = new PrintWriter(file)) {
144+
writer.println("x-entry: &entry");
145+
writer.println(" key: value");
146+
writer.println();
147+
writer.println("services:");
148+
for (int i = 0; i < 1_000; i++) {
149+
writer.println(" service" + i + ":");
150+
writer.println(" image: busybox");
151+
writer.println(" environment:");
152+
writer.println(" <<: *entry");
153+
}
154+
}
155+
assertThatNoException().isThrownBy(() -> new ParsedDockerComposeFile(file));
156+
}
132157
}

0 commit comments

Comments
 (0)