diff --git a/core/src/main/java/org/testcontainers/images/PullPolicy.java b/core/src/main/java/org/testcontainers/images/PullPolicy.java index a8b8344a8c2..12d05b6fe5a 100644 --- a/core/src/main/java/org/testcontainers/images/PullPolicy.java +++ b/core/src/main/java/org/testcontainers/images/PullPolicy.java @@ -1,6 +1,9 @@ package org.testcontainers.images; +import com.google.common.annotations.VisibleForTesting; import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.utility.TestcontainersConfiguration; import java.time.Duration; @@ -8,15 +11,54 @@ * Convenience class with logic for building common {@link ImagePullPolicy} instances. * */ +@Slf4j @UtilityClass public class PullPolicy { + @VisibleForTesting + static ImagePullPolicy instance; + + @VisibleForTesting + static ImagePullPolicy defaultImplementation = new DefaultPullPolicy(); + /** * Convenience method for returning the {@link DefaultPullPolicy} default image pull policy * @return {@link ImagePullPolicy} */ - public static ImagePullPolicy defaultPolicy() { - return new DefaultPullPolicy(); + public static synchronized ImagePullPolicy defaultPolicy() { + if (instance != null) { + return instance; + } + + String imagePullPolicyClassName = TestcontainersConfiguration.getInstance().getImagePullPolicy(); + if (imagePullPolicyClassName != null) { + log.debug("Attempting to instantiate an ImagePullPolicy with class: {}", imagePullPolicyClassName); + ImagePullPolicy configuredInstance; + try { + configuredInstance = + (ImagePullPolicy) Thread + .currentThread() + .getContextClassLoader() + .loadClass(imagePullPolicyClassName) + .getConstructor() + .newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException( + "Configured ImagePullPolicy could not be loaded: " + imagePullPolicyClassName, + e + ); + } + + log.info("Found configured Image Pull Policy: {}", configuredInstance.getClass()); + + instance = configuredInstance; + } else { + instance = defaultImplementation; + } + + log.info("Image pull policy will be performed by: {}", instance); + + return instance; } /** diff --git a/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java b/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java index 8e61ea58013..78a0e2a583c 100644 --- a/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java +++ b/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java @@ -35,7 +35,7 @@ public class RemoteDockerImage extends LazyFuture { private Future imageNameFuture; @With - private ImagePullPolicy imagePullPolicy = PullPolicy.defaultPolicy(); + ImagePullPolicy imagePullPolicy = PullPolicy.defaultPolicy(); @With private ImageNameSubstitutor imageNameSubstitutor = ImageNameSubstitutor.instance(); diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index a96f373d454..921e36f80cd 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -213,6 +213,10 @@ public String getImageSubstitutorClassName() { return getEnvVarOrProperty("image.substitutor", null); } + public String getImagePullPolicy() { + return getEnvVarOrProperty("pull.policy", null); + } + public Integer getClientPingTimeout() { return Integer.parseInt(getEnvVarOrProperty("client.ping.timeout", "10")); } diff --git a/core/src/test/java/org/testcontainers/images/OverrideImagePullPolicyTest.java b/core/src/test/java/org/testcontainers/images/OverrideImagePullPolicyTest.java new file mode 100644 index 00000000000..3b410fd5ab9 --- /dev/null +++ b/core/src/test/java/org/testcontainers/images/OverrideImagePullPolicyTest.java @@ -0,0 +1,54 @@ +package org.testcontainers.images; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mockito; +import org.testcontainers.DockerRegistryContainer; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.FakeImagePullPolicy; +import org.testcontainers.utility.MockTestcontainersConfigurationRule; +import org.testcontainers.utility.TestcontainersConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OverrideImagePullPolicyTest { + + @Rule + public MockTestcontainersConfigurationRule config = new MockTestcontainersConfigurationRule(); + + private ImagePullPolicy originalInstance; + + private ImagePullPolicy originalDefaultImplementation; + + @Before + public void setUp() { + this.originalInstance = PullPolicy.instance; + this.originalDefaultImplementation = PullPolicy.defaultImplementation; + PullPolicy.instance = null; + PullPolicy.defaultImplementation = Mockito.mock(ImagePullPolicy.class); + } + + @After + public void tearDown() { + PullPolicy.instance = originalInstance; + PullPolicy.defaultImplementation = originalDefaultImplementation; + } + + @Test + public void simpleConfigurationTest() { + Mockito + .doReturn(FakeImagePullPolicy.class.getCanonicalName()) + .when(TestcontainersConfiguration.getInstance()) + .getImagePullPolicy(); + + try (DockerRegistryContainer registry = new DockerRegistryContainer()) { + registry.start(); + GenericContainer container = new GenericContainer<>(registry.createImage()).withExposedPorts(8080); + container.start(); + assertThat(container.getImage().imagePullPolicy).isInstanceOf(FakeImagePullPolicy.class); + container.stop(); + } + } +} diff --git a/core/src/test/java/org/testcontainers/utility/FakeImagePullPolicy.java b/core/src/test/java/org/testcontainers/utility/FakeImagePullPolicy.java new file mode 100644 index 00000000000..5ecead64d4e --- /dev/null +++ b/core/src/test/java/org/testcontainers/utility/FakeImagePullPolicy.java @@ -0,0 +1,12 @@ +package org.testcontainers.utility; + +import org.testcontainers.images.AbstractImagePullPolicy; +import org.testcontainers.images.ImageData; + +public class FakeImagePullPolicy extends AbstractImagePullPolicy { + + @Override + protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) { + return false; + } +} diff --git a/docs/features/advanced_options.md b/docs/features/advanced_options.md index cd9c57f99eb..9dd86edccd5 100644 --- a/docs/features/advanced_options.md +++ b/docs/features/advanced_options.md @@ -31,6 +31,15 @@ It is possible to specify an Image Pull Policy to determine at runtime whether a [Custom image pull policy](../../core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java) inside_block:custom_image_pull_policy +You can also configure Testcontainers to use your custom implementation by using `pull.policy` + +=== "`src/test/resources/testcontainers.properties`" + ```text + pull.policy=com.mycompany.testcontainers.ExampleImagePullPolicy + ``` + +Please see [the documentation on configuration mechanisms](./configuration.md) for more information. + ## Customizing the container ### Using docker-java