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 1 commit
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.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<>();
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should think about either deprecating withCreateContainerCmdModifier or adding an overload, to avoid increasing the API surface. Maybe, as a first step, we could keep withCreateContainerCmdModifier only but make it populate localCreateContainerCustomizers?

Also, if we name it CreateContainerCmdModifier, then we could align both APIs and not have to deprecate the old one (just thinking out loud...) - WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like I forgot something when trying to overload withCreateContainerCmdModifier 🙈. Now, this is done. My only question is about the location where createContainerCmdCustomizers is being called because createContainerCmdModifiers is currently called before. I think the current implementation is ok and we should drop createContainerCmdModifiers references due to are not being used.

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