Skip to content

Commit d9519f9

Browse files
authored
Add interface to customize CreateContainerCmd (#7421)
Add `CreateContainerCmdModifier` interface to customize `CreateContainerCmd`. Customization is applied after Testcontainers configuration is set.
1 parent 6e81732 commit d9519f9

File tree

6 files changed

+77
-5
lines changed

6 files changed

+77
-5
lines changed

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

+24-5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.testcontainers.containers.wait.strategy.Wait;
4949
import org.testcontainers.containers.wait.strategy.WaitStrategy;
5050
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
51+
import org.testcontainers.core.CreateContainerCmdModifier;
5152
import org.testcontainers.images.ImagePullPolicy;
5253
import org.testcontainers.images.RemoteDockerImage;
5354
import org.testcontainers.images.builder.Transferable;
@@ -88,6 +89,7 @@
8889
import java.util.Map.Entry;
8990
import java.util.Objects;
9091
import java.util.Optional;
92+
import java.util.ServiceLoader;
9193
import java.util.Set;
9294
import java.util.UUID;
9395
import java.util.concurrent.ExecutionException;
@@ -203,7 +205,6 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
203205

204206
/**
205207
* Set during container startup
206-
*
207208
*/
208209
@Setter(AccessLevel.NONE)
209210
@VisibleForTesting
@@ -220,8 +221,6 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
220221

221222
private List<Consumer<OutputFrame>> logConsumers = new ArrayList<>();
222223

223-
private final Set<Consumer<CreateContainerCmd>> createContainerCmdModifiers = new LinkedHashSet<>();
224-
225224
private static final Set<String> AVAILABLE_IMAGE_NAME_CACHE = new HashSet<>();
226225

227226
private static final RateLimiter DOCKER_CLIENT_RATE_LIMITER = RateLimiterBuilder
@@ -238,6 +237,19 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
238237

239238
private boolean hostAccessible = false;
240239

240+
private final Set<CreateContainerCmdModifier> createContainerCmdModifiers = loadCreateContainerCmdCustomizers();
241+
242+
private Set<CreateContainerCmdModifier> loadCreateContainerCmdCustomizers() {
243+
ServiceLoader<CreateContainerCmdModifier> containerCmdCustomizers = ServiceLoader.load(
244+
CreateContainerCmdModifier.class
245+
);
246+
Set<CreateContainerCmdModifier> loadedCustomizers = new LinkedHashSet<>();
247+
for (CreateContainerCmdModifier customizer : containerCmdCustomizers) {
248+
loadedCustomizers.add(customizer);
249+
}
250+
return loadedCustomizers;
251+
}
252+
241253
public GenericContainer(@NonNull final DockerImageName dockerImageName) {
242254
this.image = new RemoteDockerImage(dockerImageName);
243255
}
@@ -890,7 +902,9 @@ private void applyConfiguration(CreateContainerCmd createCommand) {
890902
createCommand.withPrivileged(privilegedMode);
891903
}
892904

893-
createContainerCmdModifiers.forEach(hook -> hook.accept(createCommand));
905+
for (CreateContainerCmdModifier createContainerCmdModifier : this.createContainerCmdModifiers) {
906+
createCommand = createContainerCmdModifier.modify(createCommand);
907+
}
894908

895909
Map<String, String> combinedLabels = new HashMap<>();
896910
combinedLabels.putAll(labels);
@@ -1491,12 +1505,16 @@ public SELF withStartupAttempts(int attempts) {
14911505
* @return this
14921506
*/
14931507
public SELF withCreateContainerCmdModifier(Consumer<CreateContainerCmd> modifier) {
1494-
createContainerCmdModifiers.add(modifier);
1508+
this.createContainerCmdModifiers.add(cmd -> {
1509+
modifier.accept(cmd);
1510+
return cmd;
1511+
});
14951512
return self();
14961513
}
14971514

14981515
/**
14991516
* Size of /dev/shm
1517+
*
15001518
* @param bytes The number of bytes to assign the shared memory. If null, it will apply the Docker default which is 64 MB.
15011519
* @return this
15021520
*/
@@ -1507,6 +1525,7 @@ public SELF withSharedMemorySize(Long bytes) {
15071525

15081526
/**
15091527
* First class support for configuring tmpfs
1528+
*
15101529
* @param mapping path and params of tmpfs/mount flag for container
15111530
* @return this
15121531
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.testcontainers.core;
2+
3+
import com.github.dockerjava.api.command.CreateContainerCmd;
4+
5+
/**
6+
* Callback interface that can be used to customize a {@link CreateContainerCmd}.
7+
*/
8+
public interface CreateContainerCmdModifier {
9+
/**
10+
* Callback to modify a {@link CreateContainerCmd} instance.
11+
*/
12+
CreateContainerCmd modify(CreateContainerCmd createContainerCmd);
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.testcontainers.custom;
2+
3+
import com.github.dockerjava.api.command.CreateContainerCmd;
4+
import org.testcontainers.core.CreateContainerCmdModifier;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
public class TestCreateContainerCmdModifier implements CreateContainerCmdModifier {
10+
11+
@Override
12+
public CreateContainerCmd modify(CreateContainerCmd createContainerCmd) {
13+
Map<String, String> labels = new HashMap<>();
14+
labels.put("project", "testcontainers-java");
15+
labels.put("scope", "global");
16+
return createContainerCmd.withLabels(labels);
17+
}
18+
}

core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ public void customLabelTest() {
264264
final GenericContainer alpineCustomLabel = new GenericContainer<>(TestImages.ALPINE_IMAGE)
265265
.withLabel("our.custom", "label")
266266
.withCommand("top")
267+
.withCreateContainerCmdModifier(cmd -> cmd.getLabels().put("scope", "local"))
267268
) {
268269
alpineCustomLabel.start();
269270

@@ -278,6 +279,10 @@ public void customLabelTest() {
278279
.containsKey("org.testcontainers.version");
279280
assertThat(labels).as("our.custom label is present").containsKey("our.custom");
280281
assertThat(labels).as("our.custom label value is label").containsEntry("our.custom", "label");
282+
assertThat(labels)
283+
.as("project label value is testcontainers-java")
284+
.containsEntry("project", "testcontainers-java");
285+
assertThat(labels).as("scope label value is local").containsEntry("scope", "local");
281286
}
282287
}
283288

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.testcontainers.custom.TestCreateContainerCmdModifier

docs/features/advanced_options.md

+16
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ It is possible to specify an Image Pull Policy to determine at runtime whether a
3333

3434
## Customizing the container
3535

36+
### Using docker-java
37+
3638
It is possible to use the [`docker-java`](https://github.com/docker-java/docker-java) API directly to customize containers before creation. This is useful if there is a need to use advanced Docker features that are not exposed by the Testcontainers API. Any customizations you make using `withCreateContainerCmdModifier` will be applied _on top_ of the container definition that Testcontainers creates, but before it is created.
3739

3840
For example, this can be used to change the container hostname:
@@ -53,6 +55,20 @@ For example, this can be used to change the container hostname:
5355

5456
For what is possible, consult the [`docker-java CreateContainerCmd` source code](https://github.com/docker-java/docker-java/blob/3.2.1/docker-java-api/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java).
5557

58+
### Using CreateContainerCmdModifier
59+
60+
Testcontainers provides a `CreateContainerCmdModifier` to customize [`docker-java CreateContainerCmd`](https://github.com/docker-java/docker-java/blob/3.2.1/docker-java-api/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java)
61+
via Service Provider Interface (SPI) mechanism.
62+
63+
<!--codeinclude-->
64+
[CreateContainerCmd example implementation](../../core/src/test/java/org/testcontainers/custom/TestCreateContainerCmdModifier.java)
65+
<!--/codeinclude-->
66+
67+
The previous implementation should be registered in `META-INF/services/org.testcontainers.core.CreateContainerCmdModifier` file.
68+
69+
!!! warning
70+
`CreateContainerCmdModifier` implementation will apply to all containers created by Testcontainers.
71+
5672
## Parallel Container Startup
5773

5874
Usually, containers are started sequentially when more than one container is used.

0 commit comments

Comments
 (0)