Skip to content

Commit 3a02788

Browse files
committed
Add interface to customize CreateContainerCmd via SPI
Add `CreateContainerCmdCustomizer` interface to customize `CreateContainerCmd`. Customization is applied after Testcontainers configuration is set.
1 parent 5e88c3d commit 3a02788

File tree

6 files changed

+71
-0
lines changed

6 files changed

+71
-0
lines changed

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

Lines changed: 20 additions & 0 deletions
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.CreateContainerCmdCustomizer;
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;
@@ -238,6 +240,12 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
238240

239241
private boolean hostAccessible = false;
240242

243+
private final ServiceLoader<CreateContainerCmdCustomizer> globalCreateContainerCustomizers = ServiceLoader.load(
244+
CreateContainerCmdCustomizer.class
245+
);
246+
247+
private final Set<CreateContainerCmdCustomizer> localCreateContainerCustomizers = new LinkedHashSet<>();
248+
241249
public GenericContainer(@NonNull final DockerImageName dockerImageName) {
242250
this.image = new RemoteDockerImage(dockerImageName);
243251
}
@@ -377,6 +385,8 @@ private void tryStart(Instant startedAt) {
377385
CreateContainerCmd createCommand = dockerClient.createContainerCmd(dockerImageName);
378386
applyConfiguration(createCommand);
379387

388+
customizeCreateContainerCmd(createCommand);
389+
380390
createCommand.getLabels().putAll(DockerClientFactory.DEFAULT_LABELS);
381391

382392
boolean reused = false;
@@ -554,6 +564,11 @@ private void tryStart(Instant startedAt) {
554564
}
555565
}
556566

567+
private void customizeCreateContainerCmd(CreateContainerCmd createCommand) {
568+
this.globalCreateContainerCustomizers.forEach(customizer -> customizer.customize(createCommand));
569+
this.localCreateContainerCustomizers.forEach(customizer -> customizer.customize(createCommand));
570+
}
571+
557572
@VisibleForTesting
558573
Checksum hashCopiedFiles() {
559574
Checksum checksum = new Adler32();
@@ -1495,6 +1510,11 @@ public SELF withCreateContainerCmdModifier(Consumer<CreateContainerCmd> modifier
14951510
return self();
14961511
}
14971512

1513+
public SELF withCreateContainerCmdCustomizer(CreateContainerCmdCustomizer customizer) {
1514+
this.localCreateContainerCustomizers.add(customizer);
1515+
return self();
1516+
}
1517+
14981518
/**
14991519
* Size of /dev/shm
15001520
* @param bytes The number of bytes to assign the shared memory. If null, it will apply the Docker default which is 64 MB.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 CreateContainerCmdCustomizer {
9+
/**
10+
* Callback to customize a {@link CreateContainerCmd} instance.
11+
* @param createContainerCmd the create command to customize
12+
*/
13+
void customize(CreateContainerCmd createContainerCmd);
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.testcontainers.custom;
2+
3+
import com.github.dockerjava.api.command.CreateContainerCmd;
4+
import org.testcontainers.core.CreateContainerCmdCustomizer;
5+
6+
public class TestCreateContainerCmdCustomizer implements CreateContainerCmdCustomizer {
7+
8+
@Override
9+
public void customize(CreateContainerCmd createContainerCmd) {
10+
createContainerCmd.getLabels().put("project", "testcontainers-java");
11+
}
12+
}

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

Lines changed: 5 additions & 0 deletions
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+
.withCreateContainerCmdCustomizer(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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.testcontainers.custom.TestCreateContainerCmdCustomizer

docs/features/advanced_options.md

Lines changed: 19 additions & 0 deletions
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,23 @@ 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 CreateContainerCmdCustomizer
59+
60+
Testcontainers provides a `CreateContainerCmdCustomizer` 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+
whether via Service Provider Interface (SPI) mechanism **(global)** or `withCreateContainerCmdCustomizer` **(local)**.
62+
63+
<!--codeinclude-->
64+
[CreateContainerCmd example implementation](../../core/src/test/java/org/testcontainers/custom/TestCreateContainerCmdCustomizer.java)
65+
<!--/codeinclude-->
66+
67+
The previous implementation should be registered in `META-INF/services/org.testcontainers.core.CreateContainerCmdCustomizer` file.
68+
69+
!!! note
70+
Local customizations will override global ones.
71+
72+
!!! warning
73+
`CreateContainerCmdCustomizer` implementation will apply to all containers created by Testcontainers.
74+
5675
## Parallel Container Startup
5776

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

0 commit comments

Comments
 (0)