Skip to content

Commit 1dba8d1

Browse files
Add Container definition API (testcontainers#7714)
Introduce `ContainerDef` and apply it to `PortForwardingContainer` and `MongoDBContainer`. `ContainerDef` is an internal API used by `GenericContainer` to decouple the state of container creation and the running state. --------- Co-authored-by: Sergei Egorov <[email protected]>
1 parent 9cfa166 commit 1dba8d1

File tree

7 files changed

+540
-194
lines changed

7 files changed

+540
-194
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
package org.testcontainers.containers;
2+
3+
import com.github.dockerjava.api.command.CreateContainerCmd;
4+
import com.github.dockerjava.api.model.Bind;
5+
import com.github.dockerjava.api.model.ExposedPort;
6+
import com.github.dockerjava.api.model.HostConfig;
7+
import com.github.dockerjava.api.model.InternetProtocol;
8+
import com.github.dockerjava.api.model.PortBinding;
9+
import com.github.dockerjava.api.model.Ports;
10+
import lombok.Getter;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.testcontainers.DockerClientFactory;
13+
import org.testcontainers.UnstableAPI;
14+
import org.testcontainers.containers.wait.strategy.WaitStrategy;
15+
import org.testcontainers.images.RemoteDockerImage;
16+
import org.testcontainers.utility.DockerImageName;
17+
import org.testcontainers.utility.TestcontainersConfiguration;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.HashMap;
22+
import java.util.HashSet;
23+
import java.util.LinkedHashSet;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Set;
27+
28+
@UnstableAPI
29+
@Slf4j
30+
class ContainerDef {
31+
32+
@Getter
33+
private RemoteDockerImage image;
34+
35+
Set<ExposedPort> exposedPorts = new HashSet<>();
36+
37+
Set<PortBinding> portBindings = new HashSet<>();
38+
39+
Map<String, String> labels = new HashMap<>();
40+
41+
Map<String, String> envVars = new HashMap<>();
42+
43+
private String[] entrypoint;
44+
45+
private String[] command = new String[0];
46+
47+
@Getter
48+
private Network network;
49+
50+
Set<String> networkAliases = new LinkedHashSet<>();
51+
52+
@Getter
53+
private String networkMode;
54+
55+
List<Bind> binds = new ArrayList<>();
56+
57+
@Getter
58+
private boolean privilegedMode;
59+
60+
@Getter
61+
private WaitStrategy waitStrategy = GenericContainer.DEFAULT_WAIT_STRATEGY;
62+
63+
public ContainerDef() {}
64+
65+
protected void applyTo(CreateContainerCmd createCommand) {
66+
HostConfig hostConfig = createCommand.getHostConfig();
67+
if (hostConfig == null) {
68+
hostConfig = new HostConfig();
69+
createCommand.withHostConfig(hostConfig);
70+
}
71+
// PortBindings must contain:
72+
// * all exposed ports with a randomized host port (equivalent to -p CONTAINER_PORT)
73+
// * all exposed ports with a fixed host port (equivalent to -p HOST_PORT:CONTAINER_PORT)
74+
Map<ExposedPort, PortBinding> allPortBindings = new HashMap<>();
75+
// First, collect all the randomized host ports from our 'exposedPorts' field
76+
for (ExposedPort exposedPort : this.exposedPorts) {
77+
allPortBindings.put(exposedPort, new PortBinding(Ports.Binding.empty(), exposedPort));
78+
}
79+
// Next, collect all the fixed host ports from our 'portBindings' field, overwriting any randomized ports so that
80+
// we don't create two bindings for the same container port.
81+
for (PortBinding portBinding : this.portBindings) {
82+
allPortBindings.put(portBinding.getExposedPort(), portBinding);
83+
}
84+
hostConfig.withPortBindings(new ArrayList<>(allPortBindings.values()));
85+
86+
// Next, ExposedPorts must be set up to publish all of the above ports, both randomized and fixed.
87+
createCommand.withExposedPorts(new ArrayList<>(allPortBindings.keySet()));
88+
89+
createCommand.withEnv(
90+
this.envVars.entrySet()
91+
.stream()
92+
.filter(it -> it.getValue() != null)
93+
.map(it -> it.getKey() + "=" + it.getValue())
94+
.toArray(String[]::new)
95+
);
96+
97+
if (this.entrypoint != null) {
98+
createCommand.withEntrypoint(this.entrypoint);
99+
}
100+
101+
if (this.command != null) {
102+
createCommand.withCmd(this.command);
103+
}
104+
105+
if (this.network != null) {
106+
hostConfig.withNetworkMode(this.network.getId());
107+
createCommand.withAliases(this.networkAliases.toArray(new String[0]));
108+
} else {
109+
if (this.networkMode != null) {
110+
createCommand.getHostConfig().withNetworkMode(this.networkMode);
111+
}
112+
}
113+
114+
boolean shouldCheckFileMountingSupport =
115+
this.binds.size() > 0 && !TestcontainersConfiguration.getInstance().isDisableChecks();
116+
if (shouldCheckFileMountingSupport) {
117+
if (!DockerClientFactory.instance().isFileMountingSupported()) {
118+
log.warn(
119+
"Unable to mount a file from test host into a running container. " +
120+
"This may be a misconfiguration or limitation of your Docker environment. " +
121+
"Some features might not work."
122+
);
123+
}
124+
}
125+
126+
hostConfig.withBinds(this.binds.toArray(new Bind[0]));
127+
128+
if (this.privilegedMode) {
129+
createCommand.getHostConfig().withPrivileged(this.privilegedMode);
130+
}
131+
132+
Map<String, String> combinedLabels = new HashMap<>(this.labels);
133+
if (createCommand.getLabels() != null) {
134+
combinedLabels.putAll(createCommand.getLabels());
135+
}
136+
137+
createCommand.withLabels(combinedLabels);
138+
}
139+
140+
protected void setImage(RemoteDockerImage image) {
141+
this.image = image;
142+
}
143+
144+
protected void setImage(String image) {
145+
setImage(DockerImageName.parse(image));
146+
}
147+
148+
protected void setImage(DockerImageName image) {
149+
setImage(new RemoteDockerImage(image));
150+
}
151+
152+
public Set<ExposedPort> getExposedPorts() {
153+
return new HashSet<>(this.exposedPorts);
154+
}
155+
156+
protected void setExposedPorts(Set<ExposedPort> exposedPorts) {
157+
this.exposedPorts.clear();
158+
this.exposedPorts.addAll(exposedPorts);
159+
}
160+
161+
protected void addExposedPorts(ExposedPort... exposedPorts) {
162+
this.exposedPorts.addAll(Arrays.asList(exposedPorts));
163+
}
164+
165+
protected void addExposedPort(ExposedPort exposedPort) {
166+
this.exposedPorts.add(exposedPort);
167+
}
168+
169+
protected void setExposedTcpPorts(Set<Integer> ports) {
170+
this.exposedPorts.clear();
171+
ports.forEach(port -> this.exposedPorts.add(ExposedPort.tcp(port)));
172+
}
173+
174+
protected void addExposedTcpPorts(int... ports) {
175+
for (int port : ports) {
176+
this.exposedPorts.add(ExposedPort.tcp(port));
177+
}
178+
}
179+
180+
protected void addExposedTcpPort(int port) {
181+
this.exposedPorts.add(ExposedPort.tcp(port));
182+
}
183+
184+
protected void addExposedPort(int port, InternetProtocol protocol) {
185+
this.exposedPorts.add(new ExposedPort(port, protocol));
186+
}
187+
188+
public Set<PortBinding> getPortBindings() {
189+
return new HashSet<>(this.portBindings);
190+
}
191+
192+
protected void setPortBindings(Set<PortBinding> portBindings) {
193+
this.portBindings.clear();
194+
this.portBindings.addAll(portBindings);
195+
}
196+
197+
protected void addPortBindings(PortBinding... portBindings) {
198+
this.portBindings.addAll(Arrays.asList(portBindings));
199+
}
200+
201+
protected void addPortBinding(PortBinding portBinding) {
202+
this.portBindings.add(portBinding);
203+
}
204+
205+
public Map<String, String> getLabels() {
206+
return new HashMap<>(this.labels);
207+
}
208+
209+
protected void setLabels(Map<String, String> labels) {
210+
this.labels.clear();
211+
this.labels.putAll(labels);
212+
}
213+
214+
protected void addLabels(Map<String, String> labels) {
215+
this.labels.putAll(labels);
216+
}
217+
218+
protected void addLabel(String key, String value) {
219+
this.labels.put(key, value);
220+
}
221+
222+
public Map<String, String> getEnvVars() {
223+
return new HashMap<>(this.envVars);
224+
}
225+
226+
protected void setEnvVars(Map<String, String> envVars) {
227+
this.envVars.clear();
228+
this.envVars.putAll(envVars);
229+
}
230+
231+
protected void addEnvVars(Map<String, String> envVars) {
232+
this.envVars.putAll(envVars);
233+
}
234+
235+
protected void addEnvVar(String key, String value) {
236+
this.envVars.put(key, value);
237+
}
238+
239+
public String[] getEntrypoint() {
240+
return Arrays.copyOf(this.entrypoint, this.entrypoint.length);
241+
}
242+
243+
protected void setEntrypoint(String... entrypoint) {
244+
this.entrypoint = entrypoint;
245+
}
246+
247+
public String[] getCommand() {
248+
return Arrays.copyOf(this.command, this.command.length);
249+
}
250+
251+
protected void setCommand(String... command) {
252+
this.command = command;
253+
}
254+
255+
protected void setNetwork(Network network) {
256+
this.network = network;
257+
}
258+
259+
public Set<String> getNetworkAliases() {
260+
return new LinkedHashSet<>(this.networkAliases);
261+
}
262+
263+
protected void setNetworkAliases(Set<String> aliases) {
264+
this.networkAliases.clear();
265+
this.networkAliases.addAll(aliases);
266+
}
267+
268+
protected void addNetworkAliases(String... aliases) {
269+
this.networkAliases.addAll(Arrays.asList(aliases));
270+
}
271+
272+
protected void addNetworkAlias(String alias) {
273+
this.networkAliases.add(alias);
274+
}
275+
276+
protected void setNetworkMode(String networkMode) {
277+
this.networkMode = networkMode;
278+
}
279+
280+
protected void setPrivilegedMode(boolean privilegedMode) {
281+
this.privilegedMode = privilegedMode;
282+
}
283+
284+
public List<Bind> getBinds() {
285+
return new ArrayList<>(this.binds);
286+
}
287+
288+
protected void setBinds(List<Bind> binds) {
289+
this.binds.clear();
290+
this.binds.addAll(binds);
291+
}
292+
293+
protected void addBinds(Bind... binds) {
294+
this.binds.addAll(Arrays.asList(binds));
295+
}
296+
297+
protected void addBind(Bind bind) {
298+
this.binds.add(bind);
299+
}
300+
301+
protected void setWaitStrategy(WaitStrategy waitStrategy) {
302+
this.waitStrategy = waitStrategy;
303+
}
304+
}

0 commit comments

Comments
 (0)