Skip to content

Commit

Permalink
Add interface to customize CreateContainerCmd via SPI
Browse files Browse the repository at this point in the history
Add `CreateContainerCmdCustomizer` interface to customize
`CreateContainerCmd`. Customization is applied after Testcontainers
configuration is set.
  • Loading branch information
eddumelendez committed Aug 17, 2023
1 parent 5e88c3d commit 3a02788
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
import org.testcontainers.core.CreateContainerCmdCustomizer;
import org.testcontainers.images.ImagePullPolicy;
import org.testcontainers.images.RemoteDockerImage;
import org.testcontainers.images.builder.Transferable;
Expand Down Expand Up @@ -88,6 +89,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -238,6 +240,12 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>

private boolean hostAccessible = false;

private final ServiceLoader<CreateContainerCmdCustomizer> globalCreateContainerCustomizers = ServiceLoader.load(
CreateContainerCmdCustomizer.class
);

private final Set<CreateContainerCmdCustomizer> localCreateContainerCustomizers = new LinkedHashSet<>();

public GenericContainer(@NonNull final DockerImageName dockerImageName) {
this.image = new RemoteDockerImage(dockerImageName);
}
Expand Down Expand Up @@ -377,6 +385,8 @@ private void tryStart(Instant startedAt) {
CreateContainerCmd createCommand = dockerClient.createContainerCmd(dockerImageName);
applyConfiguration(createCommand);

customizeCreateContainerCmd(createCommand);

createCommand.getLabels().putAll(DockerClientFactory.DEFAULT_LABELS);

boolean reused = false;
Expand Down Expand Up @@ -554,6 +564,11 @@ private void tryStart(Instant startedAt) {
}
}

private void customizeCreateContainerCmd(CreateContainerCmd createCommand) {
this.globalCreateContainerCustomizers.forEach(customizer -> customizer.customize(createCommand));
this.localCreateContainerCustomizers.forEach(customizer -> customizer.customize(createCommand));
}

@VisibleForTesting
Checksum hashCopiedFiles() {
Checksum checksum = new Adler32();
Expand Down Expand Up @@ -1495,6 +1510,11 @@ public SELF withCreateContainerCmdModifier(Consumer<CreateContainerCmd> modifier
return self();
}

public SELF withCreateContainerCmdCustomizer(CreateContainerCmdCustomizer customizer) {
this.localCreateContainerCustomizers.add(customizer);
return self();
}

/**
* Size of /dev/shm
* @param bytes The number of bytes to assign the shared memory. If null, it will apply the Docker default which is 64 MB.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.testcontainers.core;

import com.github.dockerjava.api.command.CreateContainerCmd;

/**
* Callback interface that can be used to customize a {@link CreateContainerCmd}.
*/
public interface CreateContainerCmdCustomizer {
/**
* Callback to customize a {@link CreateContainerCmd} instance.
* @param createContainerCmd the create command to customize
*/
void customize(CreateContainerCmd createContainerCmd);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.testcontainers.custom;

import com.github.dockerjava.api.command.CreateContainerCmd;
import org.testcontainers.core.CreateContainerCmdCustomizer;

public class TestCreateContainerCmdCustomizer implements CreateContainerCmdCustomizer {

@Override
public void customize(CreateContainerCmd createContainerCmd) {
createContainerCmd.getLabels().put("project", "testcontainers-java");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ public void customLabelTest() {
final GenericContainer alpineCustomLabel = new GenericContainer<>(TestImages.ALPINE_IMAGE)
.withLabel("our.custom", "label")
.withCommand("top")
.withCreateContainerCmdCustomizer(cmd -> cmd.getLabels().put("scope", "local"))
) {
alpineCustomLabel.start();

Expand All @@ -278,6 +279,10 @@ public void customLabelTest() {
.containsKey("org.testcontainers.version");
assertThat(labels).as("our.custom label is present").containsKey("our.custom");
assertThat(labels).as("our.custom label value is label").containsEntry("our.custom", "label");
assertThat(labels)
.as("project label value is testcontainers-java")
.containsEntry("project", "testcontainers-java");
assertThat(labels).as("scope label value is local").containsEntry("scope", "local");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.testcontainers.custom.TestCreateContainerCmdCustomizer
19 changes: 19 additions & 0 deletions docs/features/advanced_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ It is possible to specify an Image Pull Policy to determine at runtime whether a

## Customizing the container

### Using docker-java

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.

For example, this can be used to change the container hostname:
Expand All @@ -53,6 +55,23 @@ For example, this can be used to change the container hostname:

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).

### Using CreateContainerCmdCustomizer

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)
whether via Service Provider Interface (SPI) mechanism **(global)** or `withCreateContainerCmdCustomizer` **(local)**.

<!--codeinclude-->
[CreateContainerCmd example implementation](../../core/src/test/java/org/testcontainers/custom/TestCreateContainerCmdCustomizer.java)
<!--/codeinclude-->

The previous implementation should be registered in `META-INF/services/org.testcontainers.core.CreateContainerCmdCustomizer` file.

!!! note
Local customizations will override global ones.

!!! warning
`CreateContainerCmdCustomizer` implementation will apply to all containers created by Testcontainers.

## Parallel Container Startup

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

0 comments on commit 3a02788

Please sign in to comment.