Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interface to customize CreateContainerCmd #7421

Merged
merged 8 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.CreateContainerCmdModifier;
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,19 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>

private boolean hostAccessible = false;

private final Set<CreateContainerCmdModifier> createContainerCmdCustomizers = loadCreateContainerCmdCustomizers();
bsideup marked this conversation as resolved.
Show resolved Hide resolved

private Set<CreateContainerCmdModifier> loadCreateContainerCmdCustomizers() {
ServiceLoader<CreateContainerCmdModifier> containerCmdCustomizers = ServiceLoader.load(
CreateContainerCmdModifier.class
);
Set<CreateContainerCmdModifier> loadedCustomizers = new HashSet<>();
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved
for (CreateContainerCmdModifier customizer : containerCmdCustomizers) {
loadedCustomizers.add(customizer);
}
return loadedCustomizers;
}

public GenericContainer(@NonNull final DockerImageName dockerImageName) {
this.image = new RemoteDockerImage(dockerImageName);
}
Expand Down Expand Up @@ -377,6 +392,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 +571,10 @@ private void tryStart(Instant startedAt) {
}
}

private void customizeCreateContainerCmd(CreateContainerCmd createCommand) {
this.createContainerCmdCustomizers.forEach(customizer -> customizer.modify(createCommand));
}

@VisibleForTesting
Checksum hashCopiedFiles() {
Checksum checksum = new Adler32();
Expand Down Expand Up @@ -1491,7 +1512,7 @@ public SELF withStartupAttempts(int attempts) {
* @return this
*/
public SELF withCreateContainerCmdModifier(Consumer<CreateContainerCmd> modifier) {
createContainerCmdModifiers.add(modifier);
this.createContainerCmdCustomizers.add(modifier::accept);
return self();
}

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 CreateContainerCmdModifier {
/**
* Callback to modify a {@link CreateContainerCmd} instance.
* @param createContainerCmd the create command to modify
*/
void modify(CreateContainerCmd createContainerCmd);
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved
}
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.CreateContainerCmdModifier;

public class TestCreateContainerCmdModifier implements CreateContainerCmdModifier {

@Override
public void modify(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")
.withCreateContainerCmdModifier(cmd -> cmd.getLabels().put("scope", "local"))
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved
) {
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.TestCreateContainerCmdModifier
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 CreateContainerCmdModifier

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)
via Service Provider Interface (SPI) mechanism.

<!--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.CreateContainerCmdModifier` 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