diff --git a/build.gradle b/build.gradle index b7830992b07..c51b24909cb 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ subprojects { } lombok { - version = '1.18.8' + version = '1.18.12' } task delombok(type: io.franzbecker.gradle.lombok.task.DelombokTask) { diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index 22a6c2408d6..95050d8f8ee 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -1,8 +1,10 @@ package org.testcontainers.containers; +import static com.google.common.collect.Lists.newArrayList; +import static org.testcontainers.utility.CommandLine.runShellCommand; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerCmd; @@ -21,6 +23,39 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.hash.Hashing; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.Adler32; +import java.util.zip.Checksum; import lombok.AccessLevel; import lombok.Data; import lombok.NonNull; @@ -62,43 +97,6 @@ import org.testcontainers.utility.ResourceReaper; import org.testcontainers.utility.TestcontainersConfiguration; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.zip.Adler32; -import java.util.zip.Checksum; - -import static com.google.common.collect.Lists.newArrayList; -import static org.testcontainers.utility.CommandLine.runShellCommand; - /** * Base class for that allows a container to be launched and controlled. */ @@ -241,7 +239,7 @@ public GenericContainer(@NonNull final RemoteDockerImage image) { */ @Deprecated public GenericContainer() { - this(TestcontainersConfiguration.getInstance().getTinyImage()); + this(TestcontainersConfiguration.getInstance().getTinyDockerImageName().asCanonicalNameString()); } /** diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index b4a40be2f16..af8e5300863 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -4,26 +4,32 @@ import com.google.common.net.HostAndPort; import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.With; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.testcontainers.utility.Versioning.Sha256Versioning; +import org.testcontainers.utility.Versioning.TagVersioning; import java.util.regex.Pattern; -@EqualsAndHashCode(exclude = "rawName") +@EqualsAndHashCode(exclude = { "rawName", "compatibleSubstituteFor" }) @AllArgsConstructor(access = AccessLevel.PRIVATE) public final class DockerImageName { /* Regex patterns used for validation */ private static final String ALPHA_NUMERIC = "[a-z0-9]+"; - private static final String SEPARATOR = "([\\.]{1}|_{1,2}|-+)"; + private static final String SEPARATOR = "([.]|_{1,2}|-+)"; private static final String REPO_NAME_PART = ALPHA_NUMERIC + "(" + SEPARATOR + ALPHA_NUMERIC + ")*"; private static final Pattern REPO_NAME = Pattern.compile(REPO_NAME_PART + "(/" + REPO_NAME_PART + ")*"); private final String rawName; private final String registry; private final String repo; - @NotNull private final Versioning versioning; + @NotNull @With(AccessLevel.PRIVATE) + private final Versioning versioning; + @Nullable @With(AccessLevel.PRIVATE) + private final DockerImageName compatibleSubstituteFor; /** * Parses a docker image name from a provided string. @@ -52,8 +58,8 @@ public DockerImageName(String fullImageName) { String remoteName; if (slashIndex == -1 || (!fullImageName.substring(0, slashIndex).contains(".") && - !fullImageName.substring(0, slashIndex).contains(":") && - !fullImageName.substring(0, slashIndex).equals("localhost"))) { + !fullImageName.substring(0, slashIndex).contains(":") && + !fullImageName.substring(0, slashIndex).equals("localhost"))) { registry = ""; remoteName = fullImageName; } else { @@ -69,8 +75,10 @@ public DockerImageName(String fullImageName) { versioning = new TagVersioning(remoteName.split(":")[1]); } else { repo = remoteName; - versioning = new TagVersioning("latest"); + versioning = Versioning.ANY; } + + compatibleSubstituteFor = null; } /** @@ -92,8 +100,8 @@ public DockerImageName(String nameWithoutTag, @NotNull String version) { String remoteName; if (slashIndex == -1 || (!nameWithoutTag.substring(0, slashIndex).contains(".") && - !nameWithoutTag.substring(0, slashIndex).contains(":") && - !nameWithoutTag.substring(0, slashIndex).equals("localhost"))) { + !nameWithoutTag.substring(0, slashIndex).contains(":") && + !nameWithoutTag.substring(0, slashIndex).equals("localhost"))) { registry = ""; remoteName = nameWithoutTag; } else { @@ -108,6 +116,8 @@ public DockerImageName(String nameWithoutTag, @NotNull String version) { repo = remoteName; versioning = new TagVersioning(version); } + + compatibleSubstituteFor = null; } /** @@ -132,7 +142,7 @@ public String getVersionPart() { * @return canonical name for the image */ public String asCanonicalNameString() { - return getUnversionedPart() + versioning.getSeparator() + versioning.toString(); + return getUnversionedPart() + versioning.getSeparator() + getVersionPart(); } @Override @@ -146,7 +156,8 @@ public String toString() { * @throws IllegalArgumentException if not valid */ public void assertValid() { - HostAndPort.fromString(registry); + //noinspection UnstableApiUsage + HostAndPort.fromString(registry); // return value ignored - this throws if registry is not a valid host:port string if (!REPO_NAME.matcher(repo).matches()) { throw new IllegalArgumentException(repo + " is not a valid Docker image name (in " + rawName + ")"); } @@ -159,63 +170,98 @@ public String getRegistry() { return registry; } + /** + * @param newTag version tag for the copy to use + * @return an immutable copy of this {@link DockerImageName} with the new version tag + */ public DockerImageName withTag(final String newTag) { - return new DockerImageName(rawName, registry, repo, new TagVersioning(newTag)); + return withVersioning(new TagVersioning(newTag)); } - private interface Versioning { - boolean isValid(); - - String getSeparator(); + /** + * Declare that this {@link DockerImageName} is a compatible substitute for another image - i.e. that this image + * behaves as the other does, and is compatible with Testcontainers' assumptions about the other image. + * + * @param otherImageName the image name of the other image + * @return an immutable copy of this {@link DockerImageName} with the compatibility declaration attached. + */ + public DockerImageName asCompatibleSubstituteFor(String otherImageName) { + return withCompatibleSubstituteFor(DockerImageName.parse(otherImageName)); } - @Data - private static class TagVersioning implements Versioning { - public static final String TAG_REGEX = "[\\w][\\w\\.\\-]{0,127}"; - private final String tag; - - TagVersioning(String tag) { - this.tag = tag; - } + /** + * Declare that this {@link DockerImageName} is a compatible substitute for another image - i.e. that this image + * behaves as the other does, and is compatible with Testcontainers' assumptions about the other image. + * + * @param otherImageName the image name of the other image + * @return an immutable copy of this {@link DockerImageName} with the compatibility declaration attached. + */ + public DockerImageName asCompatibleSubstituteFor(DockerImageName otherImageName) { + return withCompatibleSubstituteFor(otherImageName); + } - @Override - public boolean isValid() { - return tag.matches(TAG_REGEX); + /** + * Test whether this {@link DockerImageName} has declared compatibility with another image (set using + * {@link DockerImageName#asCompatibleSubstituteFor(String)} or + * {@link DockerImageName#asCompatibleSubstituteFor(DockerImageName)}. + *

+ * If a version tag part is present in the other image name, the tags must exactly match, unless it + * is 'latest'. If a version part is not present in the other image name, the tag contents are ignored. + * + * @param other the other image that we are trying to test compatibility with + * @return whether this image has declared compatibility. + */ + public boolean isCompatibleWith(DockerImageName other) { + // is this image already the same or equivalent? + if (other.equals(this)) { + return true; } - @Override - public String getSeparator() { - return ":"; + if (this.compatibleSubstituteFor == null) { + return false; } - @Override - public String toString() { - return tag; - } + return this.compatibleSubstituteFor.isCompatibleWith(other); } - @Data - private static class Sha256Versioning implements Versioning { - public static final String HASH_REGEX = "[0-9a-fA-F]{32,}"; - private final String hash; - - Sha256Versioning(String hash) { - this.hash = hash; - } - - @Override - public boolean isValid() { - return hash.matches(HASH_REGEX); + /** + * Behaves as {@link DockerImageName#isCompatibleWith(DockerImageName)} but throws an exception + * rather than returning false if a mismatch is detected. + * + * @param anyOthers the other image(s) that we are trying to check compatibility with. If more + * than one is provided, this method will check compatibility with at least one + * of them. + * @throws IllegalStateException if {@link DockerImageName#isCompatibleWith(DockerImageName)} + * returns false + */ + public void assertCompatibleWith(DockerImageName... anyOthers) { + if (anyOthers.length == 0) { + throw new IllegalArgumentException("anyOthers parameter must be non-empty"); } - @Override - public String getSeparator() { - return "@"; + for (DockerImageName anyOther : anyOthers) { + if (this.isCompatibleWith(anyOther)) { + return; + } } - @Override - public String toString() { - return "sha256:" + hash; - } + final DockerImageName exampleOther = anyOthers[0]; + + throw new IllegalStateException( + String.format( + "Failed to verify that image '%s' is a compatible substitute for '%s'. This generally means that " + + + "you are trying to use an image that Testcontainers has not been designed to use. If this is " + + + "deliberate, and if you are confident that the image is compatible, you should declare " + + + "compatibility in code using the `asCompatibleSubstituteFor` method. For example:\n" + + + " DockerImageName myImage = DockerImageName.parse(\"%s\").asCompatibleSubstituteFor(\"%s\");\n" + + + "and then use `myImage` instead.", + this.rawName, exampleOther.rawName, this.rawName, exampleOther.rawName + ) + ); } } diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 9f283ba2685..521a7519c2a 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -56,49 +56,58 @@ static AtomicReference getInstanceField() { this.properties.putAll(classpathProperties); this.properties.putAll(environmentProperties); - properties.keySet() - .forEach(key -> properties.replace(key, properties.getProperty(String.valueOf(key)).trim())); + } + + private DockerImageName getImage(final String key, final String defaultValue) { + return DockerImageName + .parse(properties.getProperty(key, defaultValue).trim()) + .asCompatibleSubstituteFor(defaultValue); } @Deprecated public String getAmbassadorContainerImage() { - return String.valueOf(properties.getOrDefault("ambassador.container.image", "richnorth/ambassador:latest")); + return getAmbassadorContainerDockerImageName().asCanonicalNameString(); + } + + @Deprecated + public DockerImageName getAmbassadorContainerDockerImageName() { + return getImage("ambassador.container.image", "richnorth/ambassador:latest"); } @Deprecated public String getSocatContainerImage() { - return String.valueOf(properties.getOrDefault("socat.container.image", "alpine/socat:latest")); + return getSocatDockerImageName().asCanonicalNameString(); } public DockerImageName getSocatDockerImageName() { - return DockerImageName.parse(getSocatContainerImage()); + return getImage("socat.container.image", "alpine/socat:latest"); } @Deprecated public String getVncRecordedContainerImage() { - return String.valueOf(properties.getOrDefault("vncrecorder.container.image", "testcontainers/vnc-recorder:1.1.0")); + return getVncDockerImageName().asCanonicalNameString(); } public DockerImageName getVncDockerImageName() { - return DockerImageName.parse(getVncRecordedContainerImage()); + return getImage("vncrecorder.container.image", "testcontainers/vnc-recorder:1.1.0"); } @Deprecated public String getDockerComposeContainerImage() { - return String.valueOf(properties.getOrDefault("compose.container.image", "docker/compose:1.24.1")); + return getDockerComposeDockerImageName().asCanonicalNameString(); } public DockerImageName getDockerComposeDockerImageName() { - return DockerImageName.parse(getDockerComposeContainerImage()); + return getImage("compose.container.image", "docker/compose:1.24.1"); } @Deprecated public String getTinyImage() { - return String.valueOf(properties.getOrDefault("tinyimage.container.image", "alpine:3.5")); + return getTinyDockerImageName().asCanonicalNameString(); } public DockerImageName getTinyDockerImageName() { - return DockerImageName.parse(getTinyImage()); + return getImage("tinyimage.container.image", "alpine:3.5"); } public boolean isRyukPrivileged() { @@ -107,20 +116,20 @@ public boolean isRyukPrivileged() { @Deprecated public String getRyukImage() { - return String.valueOf(properties.getOrDefault("ryuk.container.image", "testcontainers/ryuk:0.3.0")); + return getRyukDockerImageName().asCanonicalNameString(); } public DockerImageName getRyukDockerImageName() { - return DockerImageName.parse(getRyukImage()); + return getImage("ryuk.container.image", "testcontainers/ryuk:0.3.0"); } @Deprecated public String getSSHdImage() { - return String.valueOf(properties.getOrDefault("sshd.container.image", "testcontainers/sshd:1.0.0")); + return getSSHdDockerImageName().asCanonicalNameString(); } public DockerImageName getSSHdDockerImageName() { - return DockerImageName.parse(getSSHdImage()); + return getImage("sshd.container.image", "testcontainers/sshd:1.0.0"); } public Integer getRyukTimeout() { @@ -129,29 +138,29 @@ public Integer getRyukTimeout() { @Deprecated public String getKafkaImage() { - return String.valueOf(properties.getOrDefault("kafka.container.image", "confluentinc/cp-kafka")); + return getKafkaDockerImageName().asCanonicalNameString(); } public DockerImageName getKafkaDockerImageName() { - return DockerImageName.parse(getKafkaImage()); + return getImage("kafka.container.image", "confluentinc/cp-kafka"); } @Deprecated public String getPulsarImage() { - return String.valueOf(properties.getOrDefault("pulsar.container.image", "apachepulsar/pulsar")); + return getPulsarDockerImageName().asCanonicalNameString(); } public DockerImageName getPulsarDockerImageName() { - return DockerImageName.parse(getPulsarImage()); + return getImage("pulsar.container.image", "apachepulsar/pulsar"); } @Deprecated public String getLocalStackImage() { - return String.valueOf(properties.getOrDefault("localstack.container.image", "localstack/localstack")); + return getLocalstackDockerImageName().asCanonicalNameString(); } public DockerImageName getLocalstackDockerImageName() { - return DockerImageName.parse(getLocalStackImage()); + return getImage("localstack.container.image", "localstack/localstack"); } public boolean isDisableChecks() { diff --git a/core/src/main/java/org/testcontainers/utility/Versioning.java b/core/src/main/java/org/testcontainers/utility/Versioning.java new file mode 100644 index 00000000000..8944b3f0ba6 --- /dev/null +++ b/core/src/main/java/org/testcontainers/utility/Versioning.java @@ -0,0 +1,95 @@ +package org.testcontainers.utility; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Represents mechanisms for versioning docker images. + */ +interface Versioning { + AnyVersion ANY = new AnyVersion(); + + boolean isValid(); + + String getSeparator(); + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + class AnyVersion implements Versioning { + + @Override + public boolean isValid() { + return true; + } + + @Override + public String getSeparator() { + return ""; + } + + @Override + public String toString() { + return ""; + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof Versioning; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + } + + @EqualsAndHashCode + class TagVersioning implements Versioning { + public static final String TAG_REGEX = "[\\w][\\w.\\-]{0,127}"; + private final String tag; + + TagVersioning(String tag) { + this.tag = tag; + } + + @Override + public boolean isValid() { + return tag.matches(TAG_REGEX); + } + + @Override + public String getSeparator() { + return ":"; + } + + @Override + public String toString() { + return tag; + } + } + + @EqualsAndHashCode + class Sha256Versioning implements Versioning { + public static final String HASH_REGEX = "[0-9a-fA-F]{32,}"; + private final String hash; + + Sha256Versioning(String hash) { + this.hash = hash; + } + + @Override + public boolean isValid() { + return hash.matches(HASH_REGEX); + } + + @Override + public String getSeparator() { + return "@"; + } + + @Override + public String toString() { + return "sha256:" + hash; + } + } +} diff --git a/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java b/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java index 0afb4d956a1..e49ff2880df 100644 --- a/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java +++ b/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java @@ -59,7 +59,7 @@ public class AuthenticatedImagePullTest { private static DockerClient client; private static String testImageName; - private static String testImageNameWithTag; + private static RegistryAuthLocator mockAuthLocator; @BeforeClass public static void setUp() throws InterruptedException { @@ -68,9 +68,8 @@ public static void setUp() throws InterruptedException { String testRegistryAddress = authenticatedRegistry.getHost() + ":" + authenticatedRegistry.getFirstMappedPort(); testImageName = testRegistryAddress + "/alpine"; - testImageNameWithTag = testImageName + ":latest"; - final DockerImageName expectedName = DockerImageName.parse(testImageNameWithTag); + final DockerImageName expectedName = DockerImageName.parse(testImageName); final AuthConfig authConfig = new AuthConfig() .withUsername("testuser") .withPassword("notasecret") @@ -89,7 +88,7 @@ public static void setUp() throws InterruptedException { @Before public void removeImageFromLocalDocker() { // remove the image tag from local docker so that it must be pulled before use - client.removeImageCmd(testImageNameWithTag).withForce(true).exec(); + client.removeImageCmd(testImageName).withForce(true).exec(); } @AfterClass @@ -100,7 +99,7 @@ public static void tearDown() { @Test public void testThatAuthLocatorIsUsedForContainerCreation() { // actually start a container, which will require an authenticated pull - try (final GenericContainer container = new GenericContainer<>(DockerImageName.parse(testImageNameWithTag)) + try (final GenericContainer container = new GenericContainer<>(DockerImageName.parse(testImageName)) .withCommand("/bin/sh", "-c", "sleep 10")) { container.start(); @@ -112,7 +111,7 @@ public void testThatAuthLocatorIsUsedForContainerCreation() { public void testThatAuthLocatorIsUsedForDockerfileBuild() throws IOException { // Prepare a simple temporary Dockerfile which requires our custom private image Path tempFile = getLocalTempFile(".Dockerfile"); - String dockerFileContent = "FROM " + testImageNameWithTag; + String dockerFileContent = "FROM " + testImageName; Files.write(tempFile, dockerFileContent.getBytes()); // Start a container built from a derived image, which will require an authenticated pull @@ -136,7 +135,7 @@ public void testThatAuthLocatorIsUsedForDockerComposePull() throws IOException { "services:\n" + " privateservice:\n" + " command: /bin/sh -c 'sleep 60'\n" + - " image: " + testImageNameWithTag; + " image: " + testImageName; Files.write(tempFile, composeFileContent.getBytes()); // Start the docker compose project, which will require an authenticated pull @@ -177,9 +176,9 @@ private static void putImageInRegistry() throws InterruptedException { .getId(); // push the image to the registry - client.tagImageCmd(id, testImageName, "latest").exec(); + client.tagImageCmd(id, testImageName, "").exec(); - client.pushImageCmd(testImageNameWithTag) + client.pushImageCmd(testImageName) .exec(new ResultCallback.Adapter<>()) .awaitCompletion(1, TimeUnit.MINUTES); } diff --git a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java new file mode 100644 index 00000000000..9e7136ea6d3 --- /dev/null +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -0,0 +1,93 @@ +package org.testcontainers.utility; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.hamcrest.core.StringContains.containsString; +import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse; +import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; + + +public class DockerImageNameCompatibilityTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testPlainImage() { + DockerImageName subject = DockerImageName.parse("foo"); + + assertFalse("image name foo != bar", subject.isCompatibleWith(DockerImageName.parse("bar"))); + } + @Test + public void testNoTagTreatedAsWildcard() { + final DockerImageName subject = DockerImageName.parse("foo:4.5.6"); + /* + foo:1.2.3 != foo:4.5.6 + foo:1.2.3 ~= foo + foo:1.2.3 ~= foo:latest + + The test is effectively making sure that no tag and `latest` tag are equivalent + */ + assertFalse("foo:4.5.6 != foo:1.2.3", subject.isCompatibleWith(DockerImageName.parse("foo:1.2.3"))); + assertTrue("foo:4.5.6 ~= foo", subject.isCompatibleWith(DockerImageName.parse("foo"))); + } + + @Test + public void testImageWithAutomaticCompatibilityForFullPath() { + DockerImageName subject = DockerImageName.parse("repo/foo:1.2.3"); + + assertTrue("repo/foo:1.2.3 ~= repo/foo", subject.isCompatibleWith(DockerImageName.parse("repo/foo"))); + } + + @Test + public void testImageWithClaimedCompatibility() { + DockerImageName subject = DockerImageName.parse("foo").asCompatibleSubstituteFor("bar"); + + assertTrue("foo(bar) ~= bar", subject.isCompatibleWith(DockerImageName.parse("bar"))); + assertFalse("foo(bar) != fizz", subject.isCompatibleWith(DockerImageName.parse("fizz"))); + } + + @Test + public void testImageWithClaimedCompatibilityAndVersion() { + DockerImageName subject = DockerImageName.parse("foo:1.2.3").asCompatibleSubstituteFor("bar"); + + assertTrue("foo:1.2.3(bar) ~= bar", subject.isCompatibleWith(DockerImageName.parse("bar"))); + } + + @Test + public void testImageWithClaimedCompatibilityForFullPath() { + DockerImageName subject = DockerImageName.parse("foo").asCompatibleSubstituteFor("registry/repo/bar"); + + assertTrue("foo(registry/repo/bar) ~= registry/repo/bar", subject.isCompatibleWith(DockerImageName.parse("registry/repo/bar"))); + assertFalse("foo(registry/repo/bar) != repo/bar", subject.isCompatibleWith(DockerImageName.parse("repo/bar"))); + assertFalse("foo(registry/repo/bar) != bar", subject.isCompatibleWith(DockerImageName.parse("bar"))); + } + + @Test + public void testImageWithClaimedCompatibilityForVersion() { + DockerImageName subject = DockerImageName.parse("foo").asCompatibleSubstituteFor("bar:1.2.3"); + + assertTrue("foo(bar:1.2.3) ~= bar", subject.isCompatibleWith(DockerImageName.parse("bar"))); + assertTrue("foo(bar:1.2.3) ~= bar:1.2.3", subject.isCompatibleWith(DockerImageName.parse("bar:1.2.3"))); + assertFalse("foo(bar:1.2.3) != bar:0.0.1", subject.isCompatibleWith(DockerImageName.parse("bar:0.0.1"))); + assertFalse("foo(bar:1.2.3) != bar:2.0.0", subject.isCompatibleWith(DockerImageName.parse("bar:2.0.0"))); + assertFalse("foo(bar:1.2.3) != bar:1.2.4", subject.isCompatibleWith(DockerImageName.parse("bar:1.2.4"))); + } + + @Test + public void testAssertMethodAcceptsCompatible() { + DockerImageName subject = DockerImageName.parse("foo").asCompatibleSubstituteFor("bar"); + subject.assertCompatibleWith(DockerImageName.parse("bar")); + } + + @Test + public void testAssertMethodRejectsIncompatible() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage(containsString("Failed to verify that image 'foo' is a compatible substitute for 'bar'")); + + DockerImageName subject = DockerImageName.parse("foo"); + subject.assertCompatibleWith(DockerImageName.parse("bar")); + } +} diff --git a/core/src/test/java/org/testcontainers/utility/DockerImageNameTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameTest.java index b0ed9428a5d..34575f2b4b9 100644 --- a/core/src/test/java/org/testcontainers/utility/DockerImageNameTest.java +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameTest.java @@ -112,7 +112,7 @@ public void testParsing() { canonicalName = unversionedPart + versionSeparator + version; } else { combined = unversionedPart; - canonicalName = unversionedPart + ":latest"; + canonicalName = unversionedPart; } VisibleAssertions.context("For " + combined); @@ -124,7 +124,7 @@ public void testParsing() { if (version != null) { assertEquals(combined + " has version part: " + version, version, imageName.getVersionPart()); } else { - assertEquals(combined + " has implicit version: latest", "latest", imageName.getVersionPart()); + assertEquals(combined + " has no version specified", "", imageName.getVersionPart()); } assertEquals(combined + " has canonical name: " + canonicalName, canonicalName, imageName.asCanonicalNameString()); diff --git a/core/src/test/java/org/testcontainers/utility/TestcontainersConfigurationTest.java b/core/src/test/java/org/testcontainers/utility/TestcontainersConfigurationTest.java index f0b8fb36a37..ccd68fbd984 100644 --- a/core/src/test/java/org/testcontainers/utility/TestcontainersConfigurationTest.java +++ b/core/src/test/java/org/testcontainers/utility/TestcontainersConfigurationTest.java @@ -1,14 +1,13 @@ package org.testcontainers.utility; -import org.junit.Test; - -import java.util.Properties; -import java.util.UUID; - import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse; import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; +import java.util.Properties; +import java.util.UUID; +import org.junit.Test; + public class TestcontainersConfigurationTest { final Properties environmentProperties = new Properties(); @@ -48,7 +47,7 @@ public void shouldReadReuseFromEnvironmentOnly() { assertTrue("reuse enabled", newConfig().environmentSupportsReuse()); environmentProperties.setProperty("ryuk.container.image", " testcontainersofficial/ryuk:0.3.0 "); - assertEquals("trailing whitespace was not removed from image name property", "testcontainersofficial/ryuk:0.3.0",newConfig().getRyukImage()); + assertEquals("trailing whitespace was not removed from image name property", "testcontainersofficial/ryuk:0.3.0",newConfig().getRyukDockerImageName().asCanonicalNameString()); } diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 83bb8af0628..d36273ac8e1 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -26,7 +26,7 @@ Now your tests or any other process running on your machine can get access to ru ### Selecting Kafka version -You can select a version of Confluent Platform by passing it to the container's constructor: +You can select a specific Confluent Platform Kafka docker image by passing it to the container's constructor: [Version Constructor](../../modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java) inside_block:constructorWithVersion @@ -44,7 +44,7 @@ If for some reason you want to use an externally running Zookeeper, then just pa ## Multi-container usage -If your test needs to run some other Docker container which needs access to the Kafka, do the following: +If your test needs to run some other Docker container which needs access to Kafka, do the following: * Run your other container on the same network as Kafka container, e.g.: diff --git a/examples/mongodb-container/src/test/java/org/testcontainers/containers/MongoDbContainer.java b/examples/mongodb-container/src/test/java/org/testcontainers/containers/MongoDbContainer.java index e44f2bb982e..c4c07c1f601 100644 --- a/examples/mongodb-container/src/test/java/org/testcontainers/containers/MongoDbContainer.java +++ b/examples/mongodb-container/src/test/java/org/testcontainers/containers/MongoDbContainer.java @@ -38,7 +38,6 @@ public MongoDbContainer() { * @param image the image (e.g. {@value DEFAULT_IMAGE_AND_TAG}) to use * @deprecated use {@link MongoDbContainer(DockerImageName)} instead */ - @Deprecated public MongoDbContainer(@NotNull String image) { this(DockerImageName.parse(image)); } diff --git a/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java index 681c58f4efb..597b1c9b017 100644 --- a/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java +++ b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java @@ -25,8 +25,11 @@ */ public class CassandraContainer> extends GenericContainer { + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("cassandra"); + private static final String DEFAULT_TAG = "3.11.2"; + @Deprecated - public static final String IMAGE = "cassandra"; + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); public static final Integer CQL_PORT = 9042; private static final String CONTAINER_CONFIG_LOCATION = "/etc/cassandra"; @@ -42,19 +45,18 @@ public class CassandraContainer> extends G */ @Deprecated public CassandraContainer() { - this("cassandra:3.11.2"); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link #CassandraContainer(DockerImageName)} instead - */ - @Deprecated public CassandraContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public CassandraContainer(DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + addExposedPort(CQL_PORT); setStartupAttempts(3); this.enableJmxReporting = false; diff --git a/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java b/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java index 6bdcff7b3f2..e26ca8c8416 100644 --- a/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java +++ b/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java @@ -7,7 +7,12 @@ public class ClickHouseContainer extends JdbcDatabaseContainer { public static final String NAME = "clickhouse"; - public static final String IMAGE = "yandex/clickhouse-server"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("yandex/clickhouse-server"); + + @Deprecated + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + @Deprecated public static final String DEFAULT_TAG = "18.10.3"; @@ -27,13 +32,9 @@ public class ClickHouseContainer extends JdbcDatabaseContainer { */ @Deprecated public ClickHouseContainer() { - super(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link ClickHouseContainer(DockerImageName)} instead - */ - @Deprecated public ClickHouseContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } @@ -41,6 +42,8 @@ public ClickHouseContainer(String dockerImageName) { public ClickHouseContainer(final DockerImageName dockerImageName) { super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withExposedPorts(HTTP_PORT, NATIVE_PORT); waitingFor( new HttpWaitStrategy() diff --git a/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java b/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java index 54420155f05..2a7990413e4 100644 --- a/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java +++ b/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java @@ -6,9 +6,18 @@ import java.time.Duration; public class CockroachContainer extends JdbcDatabaseContainer { + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("cockroachdb/cockroach"); + private static final String DEFAULT_TAG = "v19.1.1"; + public static final String NAME = "cockroach"; - public static final String IMAGE = "cockroachdb/cockroach"; - public static final String IMAGE_TAG = "v19.1.1"; + + @Deprecated + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + + @Deprecated + public static final String IMAGE_TAG = DEFAULT_TAG; + private static final String JDBC_DRIVER_CLASS_NAME = "org.postgresql.Driver"; private static final String JDBC_URL_PREFIX = "jdbc:postgresql"; private static final String TEST_QUERY_STRING = "SELECT 1"; @@ -24,13 +33,9 @@ public class CockroachContainer extends JdbcDatabaseContainer { private static final int KV_SSL_PORT = 11207; - private static final String DOCKER_IMAGE_NAME = "couchbase/server"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("couchbase/server"); - private static final String VERSION = "6.5.1"; + private static final String DEFAULT_TAG = "6.5.1"; private static final ObjectMapper MAPPER = new ObjectMapper(); @@ -92,26 +92,26 @@ public class CouchbaseContainer extends GenericContainer { */ @Deprecated public CouchbaseContainer() { - this(DOCKER_IMAGE_NAME + ":" + VERSION); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** - * Creates a new couchbase container with a custom image name. + * Creates a new couchbase container with the specified image name. * * @param dockerImageName the image name that should be used. - * @deprecated use {@link CouchbaseContainer(DockerImageName)} instead */ - @Deprecated public CouchbaseContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } /** * Create a new couchbase container with the specified image name. - * @param dockerImageName + * @param dockerImageName the image name that should be used. */ public CouchbaseContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); } /** diff --git a/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java b/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java index aa6d136aaec..c1a252936ea 100644 --- a/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java +++ b/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java @@ -12,7 +12,13 @@ public class Db2Container extends JdbcDatabaseContainer { public static final String NAME = "db2"; - public static final String DEFAULT_DB2_IMAGE_NAME = "ibmcom/db2"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("ibmcom/db2"); + + @Deprecated + public static final String DEFAULT_DB2_IMAGE_NAME = DEFAULT_IMAGE_NAME.getUnversionedPart(); + + @Deprecated public static final String DEFAULT_TAG = "11.5.0.0a"; public static final int DB2_PORT = 50000; @@ -25,19 +31,18 @@ public class Db2Container extends JdbcDatabaseContainer { */ @Deprecated public Db2Container() { - this(DEFAULT_DB2_IMAGE_NAME + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link Db2Container(DockerImageName)} instead - */ - @Deprecated public Db2Container(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public Db2Container(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withPrivilegedMode(true); this.waitStrategy = new LogMessageWaitStrategy() .withRegEx(".*Setup has completed\\..*") diff --git a/modules/dynalite/src/main/java/org/testcontainers/dynamodb/DynaliteContainer.java b/modules/dynalite/src/main/java/org/testcontainers/dynamodb/DynaliteContainer.java index 6063c1a5dbe..8900964a8bb 100644 --- a/modules/dynalite/src/main/java/org/testcontainers/dynamodb/DynaliteContainer.java +++ b/modules/dynalite/src/main/java/org/testcontainers/dynamodb/DynaliteContainer.java @@ -14,7 +14,8 @@ */ public class DynaliteContainer extends GenericContainer { - private static final String IMAGE_NAME = "quay.io/testcontainers/dynalite:v1.2.1-1"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("quay.io/testcontainers/dynalite"); + private static final String DEFAULT_TAG = "v1.2.1-1"; private static final int MAPPED_PORT = 4567; /** @@ -22,20 +23,19 @@ public class DynaliteContainer extends GenericContainer { */ @Deprecated public DynaliteContainer() { - this(IMAGE_NAME); - withExposedPorts(MAPPED_PORT); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link DynaliteContainer(DockerImageName)} instead - */ - @Deprecated public DynaliteContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public DynaliteContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + withExposedPorts(MAPPED_PORT); } diff --git a/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java b/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java index 6ad0172269d..7ed660d38c6 100644 --- a/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java +++ b/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java @@ -1,16 +1,15 @@ package org.testcontainers.elasticsearch; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; + +import java.net.InetSocketAddress; +import java.time.Duration; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; import org.testcontainers.utility.Base58; import org.testcontainers.utility.DockerImageName; -import java.net.InetSocketAddress; -import java.time.Duration; - -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; - /** * Represents an elasticsearch docker instance which exposes by default port 9200 and 9300 (transport.tcp.port) * The docker image is by default fetched from docker.elastic.co/elasticsearch/elasticsearch @@ -28,35 +27,41 @@ public class ElasticsearchContainer extends GenericContainer> extends GenericContainer { - public static final String VERSION = "1.4.3"; public static final Integer INFLUXDB_PORT = 8086; - private static final String IMAGE_NAME = "influxdb"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("influxdb"); + private static final String DEFAULT_TAG = "1.4.3"; + + @Deprecated + public static final String VERSION = DEFAULT_TAG; private boolean authEnabled = true; private String admin = "admin"; @@ -32,7 +35,7 @@ public class InfluxDBContainer> extends Gen */ @Deprecated public InfluxDBContainer() { - this(VERSION); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** @@ -40,11 +43,14 @@ public InfluxDBContainer() { */ @Deprecated public InfluxDBContainer(final String version) { - this(DockerImageName.parse(IMAGE_NAME + ":" + version)); + this(DEFAULT_IMAGE_NAME.withTag(version)); } public InfluxDBContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + waitStrategy = new WaitAllStrategy() .withStrategy(Wait.forHttp("/ping").withBasicCredentials(username, password).forStatusCode(204)) .withStrategy(Wait.forListeningPort()); diff --git a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java index 4d1c4ece67b..26d699a1813 100644 --- a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java +++ b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java @@ -42,7 +42,6 @@ public abstract class JdbcDatabaseContainer { + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("confluentinc/cp-kafka"); + private static final String DEFAULT_TAG = "5.2.1"; + private static final String STARTER_SCRIPT = "/testcontainers_start.sh"; public static final int KAFKA_PORT = 9093; @@ -35,7 +38,7 @@ public class KafkaContainer extends GenericContainer { */ @Deprecated public KafkaContainer() { - this("5.2.1"); + this(TestcontainersConfiguration.getInstance().getKafkaDockerImageName().withTag(DEFAULT_TAG)); } /** @@ -43,12 +46,14 @@ public KafkaContainer() { */ @Deprecated public KafkaContainer(String confluentPlatformVersion) { - this(DockerImageName.parse(TestcontainersConfiguration.getInstance().getKafkaImage() + ":" + confluentPlatformVersion)); + this(TestcontainersConfiguration.getInstance().getKafkaDockerImageName().withTag(confluentPlatformVersion)); } public KafkaContainer(final DockerImageName dockerImageName) { super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withExposedPorts(KAFKA_PORT); // Use two listeners with different names, it will force Kafka to communicate with itself via internal diff --git a/modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java b/modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java index 71702ce8e62..6a669b2f60d 100644 --- a/modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java +++ b/modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java @@ -1,6 +1,13 @@ package org.testcontainers.containers; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + import com.google.common.collect.ImmutableMap; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -10,27 +17,19 @@ import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; -import org.junit.Rule; +import org.junit.ClassRule; import org.junit.Test; import org.rnorth.ducttape.unreliables.Unreliables; import org.testcontainers.utility.DockerImageName; -import java.time.Duration; -import java.util.Arrays; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; - public class KafkaContainerTest { private static final DockerImageName KAFKA_TEST_IMAGE = DockerImageName.parse("confluentinc/cp-kafka:5.2.1"); private static final DockerImageName ZOOKEEPER_TEST_IMAGE = DockerImageName.parse("confluentinc/cp-zookeeper:4.0.0"); // junitRule { - @Rule - public KafkaContainer kafka = new KafkaContainer(); + @ClassRule + public static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.2.1")); // } @Test @@ -43,10 +42,10 @@ public void testUsage() throws Exception { @Test - public void testUsageWithVersion() throws Exception { + public void testUsageWithSpecificImage() throws Exception { try ( // constructorWithVersion { - KafkaContainer kafka = new KafkaContainer(KAFKA_TEST_IMAGE) + KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.2.1")) // } ) { kafka.start(); @@ -58,6 +57,17 @@ public void testUsageWithVersion() throws Exception { } } + + @Test + public void testUsageWithVersion() throws Exception { + try ( + KafkaContainer kafka = new KafkaContainer("5.2.1") + ) { + kafka.start(); + testKafkaFunctionality(kafka.getBootstrapServers()); + } + } + @Test public void testExternalZookeeperWithExternalNetwork() throws Exception { try ( @@ -75,7 +85,8 @@ public void testExternalZookeeperWithExternalNetwork() throws Exception { .withEnv("ZOOKEEPER_CLIENT_PORT", "2181"); // withKafkaNetwork { - GenericContainer application = new GenericContainer("alpine").withNetwork(network) + GenericContainer application = new GenericContainer<>(DockerImageName.parse("alpine")) + .withNetwork(network) // } .withNetworkAliases("dummy") .withCommand("sleep 10000") @@ -109,8 +120,8 @@ protected void testKafkaFunctionality(String bootstrapServers) throws Exception new StringDeserializer() ); ) { - String topicName = "messages"; - consumer.subscribe(Arrays.asList(topicName)); + String topicName = "messages-" + UUID.randomUUID(); + consumer.subscribe(singletonList(topicName)); producer.send(new ProducerRecord<>(topicName, "testcontainers", "rulezzz")).get(); diff --git a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java index a9547eec94d..7532ebe0b3d 100644 --- a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java +++ b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java @@ -4,14 +4,6 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.client.builder.AwsClientBuilder; -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -24,6 +16,15 @@ import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.TestcontainersConfiguration; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + /** *

Container for Atlassian Labs Localstack, 'A fully functional local AWS cloud stack'.

*

{@link LocalStackContainer#withServices(Service...)} should be used to select which services @@ -35,11 +36,16 @@ @Slf4j public class LocalStackContainer extends GenericContainer { - public static final String VERSION = "0.11.2"; static final int PORT = 4566; private static final String HOSTNAME_EXTERNAL_ENV_VAR = "HOSTNAME_EXTERNAL"; private final List services = new ArrayList<>(); + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("localstack/localstack"); + private static final String DEFAULT_TAG = "0.11.2"; + + @Deprecated + public static final String VERSION = DEFAULT_TAG; + /** * Whether or to assume that all APIs run on different ports (when true) or are * exposed on a single port (false). From the Localstack README: @@ -59,7 +65,7 @@ public class LocalStackContainer extends GenericContainer { */ @Deprecated public LocalStackContainer() { - this(VERSION); + this(TestcontainersConfiguration.getInstance().getLocalstackDockerImageName().withTag(DEFAULT_TAG)); } /** @@ -67,7 +73,7 @@ public LocalStackContainer() { */ @Deprecated public LocalStackContainer(String version) { - this(DockerImageName.parse(TestcontainersConfiguration.getInstance().getLocalStackImage() + ":" + version)); + this(TestcontainersConfiguration.getInstance().getLocalstackDockerImageName().withTag(version)); } /** @@ -83,6 +89,9 @@ public LocalStackContainer(final DockerImageName dockerImageName) { */ public LocalStackContainer(final DockerImageName dockerImageName, boolean useLegacyMode) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + this.legacyMode = useLegacyMode; withFileSystemBind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), "/var/run/docker.sock"); diff --git a/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java index e137c186209..b43a5f237e5 100644 --- a/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java +++ b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java @@ -9,10 +9,16 @@ */ public class MariaDBContainer> extends JdbcDatabaseContainer { - public static final String NAME = "mariadb"; - public static final String IMAGE = "mariadb"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mariadb"); + + @Deprecated public static final String DEFAULT_TAG = "10.3.6"; + public static final String NAME = "mariadb"; + + @Deprecated + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + static final String DEFAULT_USER = "test"; static final String DEFAULT_PASSWORD = "test"; @@ -29,19 +35,18 @@ public class MariaDBContainer> extends JdbcD */ @Deprecated public MariaDBContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link MariaDBContainer(DockerImageName)} instead - */ - @Deprecated public MariaDBContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public MariaDBContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + addExposedPort(MARIADB_PORT); } diff --git a/modules/mockserver/src/main/java/org/testcontainers/containers/MockServerContainer.java b/modules/mockserver/src/main/java/org/testcontainers/containers/MockServerContainer.java index 2aa33931d07..451a4710b20 100644 --- a/modules/mockserver/src/main/java/org/testcontainers/containers/MockServerContainer.java +++ b/modules/mockserver/src/main/java/org/testcontainers/containers/MockServerContainer.java @@ -6,7 +6,11 @@ @Slf4j public class MockServerContainer extends GenericContainer { - public static final String VERSION = "5.5.4"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("jamesdbloom/mockserver"); + private static final String DEFAULT_TAG = "mockserver-5.5.4"; + + @Deprecated + public static final String VERSION = DEFAULT_TAG; public static final int PORT = 1080; @@ -15,7 +19,7 @@ public class MockServerContainer extends GenericContainer { */ @Deprecated public MockServerContainer() { - this(VERSION); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** @@ -23,11 +27,14 @@ public MockServerContainer() { */ @Deprecated public MockServerContainer(String version) { - this(DockerImageName.parse("jamesdbloom/mockserver:mockserver-" + version)); + this(DEFAULT_IMAGE_NAME.withTag("mockserver-" + version)); } public MockServerContainer(DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withCommand("-logLevel INFO -serverPort " + PORT); addExposedPorts(PORT); } diff --git a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java index 1527b3924db..200b3aa0820 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java @@ -16,10 +16,12 @@ */ @Slf4j public class MongoDBContainer extends GenericContainer { + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mongo"); + private static final String DEFAULT_TAG = "4.0.10"; private static final int CONTAINER_EXIT_CODE_OK = 0; private static final int MONGODB_INTERNAL_PORT = 27017; private static final int AWAIT_INIT_REPLICA_SET_ATTEMPTS = 60; - private static final String MONGODB_VERSION_DEFAULT = "4.0.10"; private static final String MONGODB_DATABASE_NAME_DEFAULT = "test"; /** @@ -27,19 +29,18 @@ public class MongoDBContainer extends GenericContainer { */ @Deprecated public MongoDBContainer() { - this("mongo:" + MONGODB_VERSION_DEFAULT); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link MongoDBContainer(DockerImageName)} instead - */ - @Deprecated public MongoDBContainer(@NonNull final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public MongoDBContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withExposedPorts(MONGODB_INTERNAL_PORT); withCommand("--replSet", "docker-rs"); waitingFor( diff --git a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainerProvider.java b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainerProvider.java index d72ab8e8419..fe7ada669bd 100644 --- a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainerProvider.java +++ b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainerProvider.java @@ -21,6 +21,7 @@ public boolean supports(ConnectionFactoryOptions options) { @Override public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) { + // TODO work out how best to do this if these constants become private String image = MSSQLServerContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION); MSSQLServerContainer container = new MSSQLServerContainer<>(image); diff --git a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java index 0a6d8149e34..a20c60fd2dc 100644 --- a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java +++ b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java @@ -11,10 +11,14 @@ */ public class MSSQLServerContainer> extends JdbcDatabaseContainer { - public static final String NAME = "sqlserver"; - public static final String IMAGE = "mcr.microsoft.com/mssql/server"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mcr.microsoft.com/mssql/server"); + @Deprecated public static final String DEFAULT_TAG = "2017-CU12"; + public static final String NAME = "sqlserver"; + + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + public static final Integer MS_SQL_SERVER_PORT = 1433; static final String DEFAULT_USER = "SA"; @@ -38,19 +42,18 @@ public class MSSQLServerContainer> exten */ @Deprecated public MSSQLServerContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link MSSQLServerContainer(DockerImageName)} instead - */ - @Deprecated public MSSQLServerContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public MSSQLServerContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withStartupTimeoutSeconds(DEFAULT_STARTUP_TIMEOUT_SECONDS); withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS); addExposedPort(MS_SQL_SERVER_PORT); diff --git a/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java b/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java index 35321db5d72..1dc235e6536 100644 --- a/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java +++ b/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java @@ -12,9 +12,15 @@ public class MySQLContainer> extends JdbcDatabaseContainer { public static final String NAME = "mysql"; - public static final String IMAGE = "mysql"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mysql"); + + @Deprecated public static final String DEFAULT_TAG = "5.7.22"; + @Deprecated + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + static final String DEFAULT_USER = "test"; static final String DEFAULT_PASSWORD = "test"; @@ -31,19 +37,18 @@ public class MySQLContainer> extends JdbcDatab */ @Deprecated public MySQLContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link MySQLContainer(DockerImageName)} instead - */ - @Deprecated public MySQLContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public MySQLContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + addExposedPort(MYSQL_PORT); } diff --git a/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java b/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java index 5d4212ccacd..e93a840302a 100644 --- a/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java +++ b/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java @@ -1,5 +1,11 @@ package org.testcontainers.containers; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.util.stream.Collectors.toSet; + +import java.time.Duration; +import java.util.Set; +import java.util.stream.Stream; import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import org.testcontainers.containers.wait.strategy.WaitAllStrategy; @@ -8,13 +14,6 @@ import org.testcontainers.utility.LicenseAcceptance; import org.testcontainers.utility.MountableFile; -import java.time.Duration; -import java.util.Set; -import java.util.stream.Stream; - -import static java.net.HttpURLConnection.HTTP_OK; -import static java.util.stream.Collectors.toSet; - /** * Testcontainer for Neo4j. * @@ -26,14 +25,13 @@ public class Neo4jContainer> extends GenericContaine /** * The image defaults to the official Neo4j image: Neo4j. */ - private static final String DEFAULT_IMAGE_NAME = "neo4j"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("neo4j"); /** * The default tag (version) to use. */ private static final String DEFAULT_TAG = "3.5.0"; - - private static final String DOCKER_IMAGE_NAME = DEFAULT_IMAGE_NAME + ":" + DEFAULT_TAG; + private static final String ENTERPRISE_TAG = DEFAULT_TAG + "-enterprise"; /** * Default port for the binary Bolt protocol. @@ -57,33 +55,41 @@ public class Neo4jContainer> extends GenericContaine private static final String AUTH_FORMAT = "neo4j/%s"; - private String adminPassword = DEFAULT_ADMIN_PASSWORD; + private final boolean standardImage; - private boolean standardImage = false; + private String adminPassword = DEFAULT_ADMIN_PASSWORD; /** - * Creates a Testcontainer using the official Neo4j docker image. + * Creates a Neo4jContainer using the official Neo4j docker image. * @deprecated use {@link Neo4jContainer(DockerImageName)} instead */ @Deprecated public Neo4jContainer() { - this(DOCKER_IMAGE_NAME); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** - * Creates a Testcontainer using a specific docker image. + * Creates a Neo4jContainer using a specific docker image. * * @param dockerImageName The docker image to use. - * @deprecated use {@link Neo4jContainer(DockerImageName)} instead */ - @Deprecated public Neo4jContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } + /** + * Creates a Neo4jContainer using a specific docker image. + * + * @param dockerImageName The docker image to use. + */ public Neo4jContainer(final DockerImageName dockerImageName) { super(dockerImageName); + this.standardImage = dockerImageName.getUnversionedPart() + .equals(DEFAULT_IMAGE_NAME.getUnversionedPart()); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + WaitStrategy waitForBolt = new LogMessageWaitStrategy() .withRegEx(String.format(".*Bolt enabled on 0\\.0\\.0\\.0:%d\\.\n", DEFAULT_BOLT_PORT)); WaitStrategy waitForHttp = new HttpWaitStrategy() @@ -96,10 +102,6 @@ public Neo4jContainer(final DockerImageName dockerImageName) { .withStartupTimeout(Duration.ofMinutes(2)); addExposedPorts(DEFAULT_BOLT_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT); - - if (dockerImageName.getUnversionedPart().equals(DEFAULT_IMAGE_NAME)) { - this.standardImage = true; - } } @Override @@ -149,13 +151,13 @@ public String getHttpsUrl() { * @return This container. */ public S withEnterpriseEdition() { - if (!standardImage) { throw new IllegalStateException( - String.format("Cannot use enterprise version with alternative image %s.", getDockerImageName())); + String.format("Cannot use enterprise version with alternative image %s.", + getDockerImageName())); } - setDockerImageName(DOCKER_IMAGE_NAME + "-enterprise"); + setDockerImageName(DEFAULT_IMAGE_NAME.withTag(ENTERPRISE_TAG).asCanonicalNameString()); LicenseAcceptance.assertLicenseAccepted(getDockerImageName()); addEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes"); diff --git a/modules/nginx/src/main/java/org/testcontainers/containers/NginxContainer.java b/modules/nginx/src/main/java/org/testcontainers/containers/NginxContainer.java index 75a4571b68e..296d82b7997 100644 --- a/modules/nginx/src/main/java/org/testcontainers/containers/NginxContainer.java +++ b/modules/nginx/src/main/java/org/testcontainers/containers/NginxContainer.java @@ -15,28 +15,28 @@ public class NginxContainer> extends GenericContainer implements LinkableContainer { private static final int NGINX_DEFAULT_PORT = 80; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("nginx"); + private static final String DEFAULT_TAG = "1.9.4"; /** * @deprecated use {@link NginxContainer(DockerImageName)} instead */ @Deprecated public NginxContainer() { - this("nginx:1.9.4"); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link NginxContainer(DockerImageName)} instead - */ - @Deprecated public NginxContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); - - addExposedPort(NGINX_DEFAULT_PORT); - setCommand("nginx", "-g", "daemon off;"); } public NginxContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + addExposedPort(NGINX_DEFAULT_PORT); + setCommand("nginx", "-g", "daemon off;"); } @NotNull diff --git a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java index 8406d8275fb..b5700c1e84e 100644 --- a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java +++ b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java @@ -41,10 +41,6 @@ public OracleContainer() { this(resolveImageName()); } - /** - * @deprecated use {@link OracleContainer(DockerImageName)} instead - */ - @Deprecated public OracleContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } diff --git a/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java b/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java index 797f73f7159..f6ce060b6ec 100644 --- a/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java +++ b/modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java @@ -29,9 +29,8 @@ public class OrientDBContainer extends GenericContainer { private static final Logger LOGGER = LoggerFactory.getLogger(OrientDBContainer.class); - private static final String DEFAULT_IMAGE_NAME = "orientdb"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("orientdb"); private static final String DEFAULT_TAG = "3.0.24-tp3"; - private static final String DOCKER_IMAGE_NAME = DEFAULT_IMAGE_NAME + ":" + DEFAULT_TAG; private static final String DEFAULT_USERNAME = "admin"; private static final String DEFAULT_PASSWORD = "admin"; @@ -54,13 +53,9 @@ public class OrientDBContainer extends GenericContainer { */ @Deprecated public OrientDBContainer() { - this(DOCKER_IMAGE_NAME); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link OrientDBContainer(DockerImageName)} instead - */ - @Deprecated public OrientDBContainer(@NonNull String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } @@ -68,6 +63,8 @@ public OrientDBContainer(@NonNull String dockerImageName) { public OrientDBContainer(final DockerImageName dockerImageName) { super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + serverPassword = DEFAULT_SERVER_PASSWORD; databaseName = DEFAULT_DATABASE_NAME; diff --git a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgisContainerProvider.java b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgisContainerProvider.java index 9e59787837b..76c213f0931 100644 --- a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgisContainerProvider.java +++ b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgisContainerProvider.java @@ -9,8 +9,8 @@ public class PostgisContainerProvider extends JdbcDatabaseContainerProvider { private static final String NAME = "postgis"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("postgis/postgis").asCompatibleSubstituteFor("postgres"); private static final String DEFAULT_TAG = "12-3.0"; - private static final String DEFAULT_IMAGE = "postgis/postgis"; public static final String USER_PARAM = "user"; public static final String PASSWORD_PARAM = "password"; @@ -27,7 +27,7 @@ public JdbcDatabaseContainer newInstance() { @Override public JdbcDatabaseContainer newInstance(String tag) { - return new PostgreSQLContainer(DockerImageName.parse(DEFAULT_IMAGE).withTag(DEFAULT_TAG)); + return new PostgreSQLContainer<>(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } @Override diff --git a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java index b1ddb2a26ee..5a4abe6d0ee 100644 --- a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java +++ b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java @@ -16,7 +16,9 @@ public class PostgreSQLContainer> extends JdbcDatabaseContainer { public static final String NAME = "postgresql"; public static final String IMAGE = "postgres"; + public static final String DEFAULT_TAG = "9.6.12"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("postgres"); public static final Integer POSTGRESQL_PORT = 5432; @@ -37,19 +39,18 @@ public class PostgreSQLContainer> extends */ @Deprecated public PostgreSQLContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link PostgreSQLContainer(DockerImageName)} instead - */ - @Deprecated public PostgreSQLContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public PostgreSQLContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + this.waitStrategy = new LogMessageWaitStrategy() .withRegEx(".*database system is ready to accept connections.*\\s") .withTimes(2) diff --git a/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java b/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java index 1c8c03ce4de..e4934693d14 100644 --- a/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java +++ b/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java @@ -16,6 +16,7 @@ public class PrestoContainer> extends JdbcDatabaseContainer { public static final String NAME = "presto"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("prestosql/presto"); public static final String IMAGE = "prestosql/presto"; public static final String DEFAULT_TAG = "329"; @@ -29,19 +30,18 @@ public class PrestoContainer> extends JdbcDat */ @Deprecated public PrestoContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link PrestoContainer(DockerImageName)} instead - */ - @Deprecated public PrestoContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public PrestoContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + this.waitStrategy = new LogMessageWaitStrategy() .withRegEx(".*======== SERVER STARTED ========.*") .withStartupTimeout(Duration.of(60, SECONDS)); diff --git a/modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java b/modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java index 4d30d947113..bceff8927d7 100644 --- a/modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java +++ b/modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java @@ -14,8 +14,9 @@ public class PulsarContainer extends GenericContainer { public static final int BROKER_HTTP_PORT = 8080; public static final String METRICS_ENDPOINT = "/metrics"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("apachepulsar/pulsar"); @Deprecated - private static final String DEFAULT_PULSAR_VERSION = "2.2.0"; + private static final String DEFAULT_TAG = "2.2.0"; private boolean functionsWorkerEnabled = false; @@ -24,7 +25,7 @@ public class PulsarContainer extends GenericContainer { */ @Deprecated public PulsarContainer() { - this(DEFAULT_PULSAR_VERSION); + this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(DEFAULT_TAG)); } /** @@ -37,6 +38,9 @@ public PulsarContainer(String pulsarVersion) { public PulsarContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DockerImageName.parse("apachepulsar/pulsar")); + withExposedPorts(BROKER_PORT, BROKER_HTTP_PORT); withCommand("/pulsar/bin/pulsar", "standalone", "--no-functions-worker", "-nss"); waitingFor(Wait.forHttp(METRICS_ENDPOINT).forStatusCode(200).forPort(BROKER_HTTP_PORT)); diff --git a/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java b/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java index a3b6134b29c..edbffcf3174 100644 --- a/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java +++ b/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java @@ -28,7 +28,7 @@ public class RabbitMQContainer extends GenericContainer { /** * The image defaults to the official RabbitmQ image: RabbitMQ. */ - private static final String DEFAULT_IMAGE_NAME = "rabbitmq"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("rabbitmq"); private static final String DEFAULT_TAG = "3.7.25-management-alpine"; private static final int DEFAULT_AMQP_PORT = 5672; @@ -41,21 +41,19 @@ public class RabbitMQContainer extends GenericContainer { private final List> values = new ArrayList<>(); /** - * Creates a Testcontainer using the official RabbitMQ docker image. + * Creates a RabbitMQ container using the official RabbitMQ docker image. * @deprecated use {@link RabbitMQContainer(DockerImageName)} instead */ @Deprecated public RabbitMQContainer() { - this(DEFAULT_IMAGE_NAME + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** - * Creates a Testcontainer using a specific docker image. + * Creates a RabbitMQ container using a specific docker image. * * @param dockerImageName The docker image to use. - * @deprecated use {@link RabbitMQContainer(DockerImageName)} instead */ - @Deprecated public RabbitMQContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } @@ -63,6 +61,8 @@ public RabbitMQContainer(String dockerImageName) { public RabbitMQContainer(final DockerImageName dockerImageName) { super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + addExposedPorts(DEFAULT_AMQP_PORT, DEFAULT_AMQPS_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT); this.waitStrategy = Wait. diff --git a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java index f1cedab76ad..d60d9191748 100644 --- a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java +++ b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java @@ -1,10 +1,21 @@ package org.testcontainers.containers; +import static java.time.temporal.ChronoUnit.SECONDS; + import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.model.AccessMode; import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.Volume; import com.google.common.collect.ImmutableSet; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.time.Duration; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.openqa.selenium.Capabilities; @@ -25,18 +36,6 @@ import org.testcontainers.lifecycle.TestLifecycleAware; import org.testcontainers.utility.DockerImageName; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.time.Duration; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static java.time.temporal.ChronoUnit.SECONDS; - /** * A chrome/firefox/custom container based on SeleniumHQ's standalone container sets. *

@@ -44,8 +43,14 @@ */ public class BrowserWebDriverContainer> extends GenericContainer implements LinkableContainer, TestLifecycleAware { - private static final String CHROME_IMAGE = "selenium/standalone-chrome-debug:%s"; - private static final String FIREFOX_IMAGE = "selenium/standalone-firefox-debug:%s"; + private static final DockerImageName CHROME_IMAGE = DockerImageName.parse("selenium/standalone-chrome-debug"); + private static final DockerImageName FIREFOX_IMAGE = DockerImageName.parse("selenium/standalone-firefox-debug"); + private static final DockerImageName[] COMPATIBLE_IMAGES = new DockerImageName[] { + CHROME_IMAGE, + FIREFOX_IMAGE, + DockerImageName.parse("selenium/standalone-chrome"), + DockerImageName.parse("selenium/standalone-firefox") + }; private static final String DEFAULT_PASSWORD = "secret"; private static final int SELENIUM_PORT = 4444; @@ -56,7 +61,7 @@ public class BrowserWebDriverContainer chrome = new BrowserWebDriverContainer<>(imageName) .withCapabilities(new ChromeOptions())) { chrome.start(); diff --git a/modules/selenium/src/test/java/org/testcontainers/junit/SpecificImageNameWebDriverContainerTest.java b/modules/selenium/src/test/java/org/testcontainers/junit/SpecificImageNameWebDriverContainerTest.java index bbc3b4db7a0..9a15c6b466d 100644 --- a/modules/selenium/src/test/java/org/testcontainers/junit/SpecificImageNameWebDriverContainerTest.java +++ b/modules/selenium/src/test/java/org/testcontainers/junit/SpecificImageNameWebDriverContainerTest.java @@ -8,7 +8,8 @@ public class SpecificImageNameWebDriverContainerTest extends BaseWebDriverContainerTest { - private static final DockerImageName FIREFOX_IMAGE = DockerImageName.parse("selenium/standalone-firefox:2.53.1-beryllium"); + private static final DockerImageName FIREFOX_IMAGE = DockerImageName + .parse("selenium/standalone-firefox:2.53.1-beryllium"); @Rule public BrowserWebDriverContainer firefox = new BrowserWebDriverContainer<>(FIREFOX_IMAGE) diff --git a/modules/solr/src/main/java/org/testcontainers/containers/SolrContainer.java b/modules/solr/src/main/java/org/testcontainers/containers/SolrContainer.java index aeadb664089..4bc2f19ec86 100644 --- a/modules/solr/src/main/java/org/testcontainers/containers/SolrContainer.java +++ b/modules/solr/src/main/java/org/testcontainers/containers/SolrContainer.java @@ -18,7 +18,12 @@ */ public class SolrContainer extends GenericContainer { - public static final String IMAGE = "solr"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("solr"); + + @Deprecated + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + + @Deprecated public static final String DEFAULT_TAG = "8.3.0"; public static final Integer ZOOKEEPER_PORT = 9983; @@ -31,19 +36,21 @@ public class SolrContainer extends GenericContainer { */ @Deprecated public SolrContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** * @deprecated use {@link SolrContainer(DockerImageName)} instead */ - @Deprecated public SolrContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public SolrContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + this.waitStrategy = new LogMessageWaitStrategy() .withRegEx(".*o\\.e\\.j\\.s\\.Server Started.*") .withStartupTimeout(Duration.of(60, SECONDS)); diff --git a/modules/toxiproxy/src/main/java/org/testcontainers/containers/ToxiproxyContainer.java b/modules/toxiproxy/src/main/java/org/testcontainers/containers/ToxiproxyContainer.java index b01d031cc5b..e6c143fa358 100644 --- a/modules/toxiproxy/src/main/java/org/testcontainers/containers/ToxiproxyContainer.java +++ b/modules/toxiproxy/src/main/java/org/testcontainers/containers/ToxiproxyContainer.java @@ -21,7 +21,8 @@ */ public class ToxiproxyContainer extends GenericContainer { - private static final String IMAGE_NAME = "shopify/toxiproxy:2.1.0"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("shopify/toxiproxy"); + private static final String DEFAULT_TAG = "2.1.0"; private static final int TOXIPROXY_CONTROL_PORT = 8474; private static final int FIRST_PROXIED_PORT = 8666; private static final int LAST_PROXIED_PORT = 8666 + 31; @@ -35,19 +36,18 @@ public class ToxiproxyContainer extends GenericContainer { */ @Deprecated public ToxiproxyContainer() { - this(IMAGE_NAME); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link ToxiproxyContainer(DockerImageName)} instead - */ - @Deprecated public ToxiproxyContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } public ToxiproxyContainer(final DockerImageName dockerImageName) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + addExposedPorts(TOXIPROXY_CONTROL_PORT); setWaitStrategy(new HttpWaitStrategy().forPath("/version").forPort(TOXIPROXY_CONTROL_PORT)); diff --git a/modules/vault/src/main/java/org/testcontainers/vault/VaultContainer.java b/modules/vault/src/main/java/org/testcontainers/vault/VaultContainer.java index 1532f463f37..b0c8a2a40e3 100644 --- a/modules/vault/src/main/java/org/testcontainers/vault/VaultContainer.java +++ b/modules/vault/src/main/java/org/testcontainers/vault/VaultContainer.java @@ -23,6 +23,9 @@ */ public class VaultContainer> extends GenericContainer { + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("vault"); + private static final String DEFAULT_TAG = "1.1.3"; + private static final int VAULT_PORT = 8200; private Map> secretsMap = new HashMap<>(); @@ -34,13 +37,9 @@ public class VaultContainer> extends GenericCo */ @Deprecated public VaultContainer() { - this("vault:1.1.3"); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - /** - * @deprecated use {@link VaultContainer(DockerImageName)} instead - */ - @Deprecated public VaultContainer(String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } @@ -48,6 +47,8 @@ public VaultContainer(String dockerImageName) { public VaultContainer(final DockerImageName dockerImageName) { super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + // Use the vault healthcheck endpoint to check for readiness, per https://www.vaultproject.io/api/system/health.html setWaitStrategy(Wait.forHttp("/v1/sys/health").forStatusCode(200));