From 484f3cd3bf33f5cb20cc7e3e96eba7e589afe62d Mon Sep 17 00:00:00 2001 From: Richard North Date: Fri, 14 Aug 2020 15:37:25 +0100 Subject: [PATCH 01/25] Implement image compatibility checks So that compatibility assurances can be made in code rather than just being assumed. --- .../utility/DockerImageName.java | 166 +++++++++++------- .../utility/TestcontainersConfiguration.java | 49 ++++-- .../testcontainers/utility/Versioning.java | 64 +++++++ .../DockerImageNameCompatibilityTest.java | 94 ++++++++++ .../containers/CassandraContainer.java | 15 +- .../containers/ClickHouseContainer.java | 15 +- .../containers/CockroachContainer.java | 21 ++- .../couchbase/CouchbaseContainer.java | 14 +- .../containers/Db2Container.java | 17 +- .../dynamodb/DynaliteContainer.java | 14 +- .../elasticsearch/ElasticsearchContainer.java | 19 +- .../ElasticsearchContainerTest.java | 5 +- .../containers/InfluxDBContainer.java | 14 +- .../containers/KafkaContainer.java | 9 +- .../localstack/LocalStackContainer.java | 31 ++-- .../containers/MariaDBContainer.java | 19 +- .../containers/MockServerContainer.java | 13 +- .../containers/MongoDBContainer.java | 13 +- .../MSSQLR2DBCDatabaseContainerProvider.java | 1 + .../containers/MSSQLServerContainer.java | 17 +- .../containers/MySQLContainer.java | 17 +- .../containers/Neo4jContainer.java | 33 ++-- modules/neo4j/src/test/resources/.gitignore | 1 + .../containers/NginxContainer.java | 16 +- .../containers/OracleContainer.java | 4 - .../containers/OrientDBContainer.java | 11 +- .../containers/PostgisContainerProvider.java | 4 +- .../containers/PostgreSQLContainer.java | 11 +- .../containers/PrestoContainer.java | 10 +- .../containers/PulsarContainer.java | 10 +- .../containers/RabbitMQContainer.java | 12 +- .../containers/BrowserWebDriverContainer.java | 31 ++-- .../junit/BaseWebDriverContainerTest.java | 5 +- .../junit/ChromeWebDriverContainerTest.java | 2 +- .../testcontainers/junit/Selenium3xTest.java | 4 +- ...ecificImageNameWebDriverContainerTest.java | 4 +- .../containers/SolrContainer.java | 12 +- .../containers/ToxiproxyContainer.java | 12 +- .../testcontainers/vault/VaultContainer.java | 11 +- 39 files changed, 549 insertions(+), 271 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/utility/Versioning.java create mode 100644 core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java create mode 100644 modules/neo4j/src/test/resources/.gitignore diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index b4a40be2f16..cd4c7a36f31 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -2,28 +2,27 @@ import com.google.common.net.HostAndPort; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.regex.Pattern; @EqualsAndHashCode(exclude = "rawName") -@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; + private final Versioning versioning; + @Nullable + private final DockerImageName compatibleSubstituteFor; /** * Parses a docker image name from a provided string. @@ -52,8 +51,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 { @@ -63,14 +62,16 @@ public DockerImageName(String fullImageName) { if (remoteName.contains("@sha256:")) { repo = remoteName.split("@sha256:")[0]; - versioning = new Sha256Versioning(remoteName.split("@sha256:")[1]); + versioning = new Versioning.Sha256Versioning(remoteName.split("@sha256:")[1]); } else if (remoteName.contains(":")) { repo = remoteName.split(":")[0]; - versioning = new TagVersioning(remoteName.split(":")[1]); + versioning = new Versioning.TagVersioning(remoteName.split(":")[1]); } else { repo = remoteName; - versioning = new TagVersioning("latest"); + versioning = Versioning.TagVersioning.LATEST; } + + compatibleSubstituteFor = null; } /** @@ -92,8 +93,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 { @@ -103,11 +104,26 @@ public DockerImageName(String nameWithoutTag, @NotNull String version) { if (version.startsWith("sha256:")) { repo = remoteName; - versioning = new Sha256Versioning(version.replace("sha256:", "")); + versioning = new Versioning.Sha256Versioning(version.replace("sha256:", "")); } else { repo = remoteName; - versioning = new TagVersioning(version); + versioning = new Versioning.TagVersioning(version); } + + compatibleSubstituteFor = null; + } + + private DockerImageName(String rawName, + String registry, + String repo, + @Nullable Versioning versioning, + @Nullable DockerImageName compatibleSubstituteFor) { + + this.rawName = rawName; + this.registry = registry; + this.repo = repo; + this.versioning = versioning; + this.compatibleSubstituteFor = compatibleSubstituteFor; } /** @@ -125,14 +141,14 @@ public String getUnversionedPart() { * @return the versioned part of this name (tag or sha256) */ public String getVersionPart() { - return versioning.toString(); + return versioning == null ? "latest" : versioning.toString(); } /** * @return canonical name for the image */ public String asCanonicalNameString() { - return getUnversionedPart() + versioning.getSeparator() + versioning.toString(); + return getUnversionedPart() + (versioning == null ? ":" : versioning.getSeparator()) + getVersionPart(); } @Override @@ -146,11 +162,12 @@ public String toString() { * @throws IllegalArgumentException if not valid */ public void assertValid() { + //noinspection UnstableApiUsage HostAndPort.fromString(registry); if (!REPO_NAME.matcher(repo).matches()) { throw new IllegalArgumentException(repo + " is not a valid Docker image name (in " + rawName + ")"); } - if (!versioning.isValid()) { + if (versioning != null && !versioning.isValid()) { throw new IllegalArgumentException(versioning + " is not a valid image versioning identifier (in " + rawName + ")"); } } @@ -159,63 +176,86 @@ 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 new DockerImageName(rawName, registry, repo, new Versioning.TagVersioning(newTag), compatibleSubstituteFor); } - 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 asCompatibleSubstituteFor(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; - } - - @Override - public boolean isValid() { - return tag.matches(TAG_REGEX); - } - - @Override - public String getSeparator() { - return ":"; - } - - @Override - public String toString() { - return 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 new DockerImageName(rawName, registry, repo, versioning, otherImageName); } - @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; + /** + * 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? + final boolean thisRegistrySame = other.registry.equals(this.registry); + final boolean thisRepoSame = other.repo.equals(this.repo); + final boolean thisVersioningNotSpecifiedOrSame = other.versioning == null || + other.versioning.equals(Versioning.TagVersioning.LATEST) || + other.versioning.equals(this.versioning); + + if (thisRegistrySame && thisRepoSame && thisVersioningNotSpecifiedOrSame) { + return true; } - @Override - public boolean isValid() { - return hash.matches(HASH_REGEX); + if (this.compatibleSubstituteFor == null) { + return false; } - @Override - public String getSeparator() { - return "@"; - } + return this.compatibleSubstituteFor.isCompatibleWith(other); + } - @Override - public String toString() { - return "sha256:" + hash; + /** + * Behaves as {@link DockerImageName#isCompatibleWith(DockerImageName)} but throws an exception rather than + * returning false if a mismatch is detected. + * + * @param other the other image that we are trying to check compatibility with + * @throws IllegalStateException if {@link DockerImageName#isCompatibleWith(DockerImageName)} returns false + */ + public void assertCompatibleWith(DockerImageName other) { + if (!this.isCompatibleWith(other)) { + 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, other.rawName, this.rawName, other.rawName + ) + ); } } } diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 1939e8e21b3..653e0e7a9af 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -58,45 +58,56 @@ static AtomicReference getInstanceField() { this.properties.putAll(environmentProperties); } + private DockerImageName getImage(final String key, final String defaultValue) { + return DockerImageName + .parse(properties.getProperty(key, defaultValue)) + .asCompatibleSubstituteFor(defaultValue); + } + @Deprecated public String getAmbassadorContainerImage() { - return (String) 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) 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) 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) 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) 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() { @@ -105,20 +116,20 @@ public boolean isRyukPrivileged() { @Deprecated public String getRyukImage() { - return (String) 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) 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() { @@ -127,29 +138,29 @@ public Integer getRyukTimeout() { @Deprecated public String getKafkaImage() { - return (String) 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) 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) 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..cfa63d13fae --- /dev/null +++ b/core/src/main/java/org/testcontainers/utility/Versioning.java @@ -0,0 +1,64 @@ +package org.testcontainers.utility; + +import lombok.Data; + +/** + * Represents mechanisms for versioning docker images. + */ +interface Versioning { + boolean isValid(); + + String getSeparator(); + + @Data + 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; + } + + static final TagVersioning LATEST = new TagVersioning("latest"); + } + + @Data + 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/DockerImageNameCompatibilityTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java new file mode 100644 index 00000000000..3f33ae0b37e --- /dev/null +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -0,0 +1,94 @@ +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 testLatestTreatedAsWildcard() { + final DockerImageName subject = DockerImageName.parse("foo:4.5.6"); + + assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:1.2.3").withTag("latest"))); + } + + @Test + public void testImageWithAutomaticCompatibility() { + DockerImageName subject = DockerImageName.parse("foo:1.2.3"); + + assertTrue("foo:1.2.3 ~= 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"))); + assertTrue("foo(bar:1.2.3) ~= bar:latest", subject.isCompatibleWith(DockerImageName.parse("bar:latest"))); + 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 testCheckMethodAcceptsCompatible() { + DockerImageName subject = DockerImageName.parse("foo").asCompatibleSubstituteFor("bar"); + subject.assertCompatibleWith(DockerImageName.parse("bar")); + } + + @Test + public void testCheckMethodRejectsIncompatible() { + 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/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java index 681c58f4efb..ded2a1176af 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"; @@ -40,21 +43,19 @@ public class CassandraContainer> extends G /** * @deprecated use {@link #CassandraContainer(DockerImageName)} instead */ - @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..a1bb6d72aee 100644 --- a/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java +++ b/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java @@ -28,35 +28,40 @@ 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/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java index 2451278b1c2..9a2b0c6ed4c 100644 --- a/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java +++ b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java @@ -18,6 +18,9 @@ */ public class KafkaContainer extends GenericContainer { + 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(DEFAULT_IMAGE_NAME.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/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java index 3a9fab26ae4..aa00d914832 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; @@ -23,6 +15,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 @@ -34,11 +35,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: @@ -58,7 +64,7 @@ public class LocalStackContainer extends GenericContainer { */ @Deprecated public LocalStackContainer() { - this(VERSION); + this(TestcontainersConfiguration.getInstance().getLocalstackDockerImageName().withTag(DEFAULT_TAG)); } /** @@ -66,7 +72,7 @@ public LocalStackContainer() { */ @Deprecated public LocalStackContainer(String version) { - this(DockerImageName.parse(TestcontainersConfiguration.getInstance().getLocalStackImage() + ":" + version)); + this( TestcontainersConfiguration.getInstance().getLocalstackDockerImageName().withTag(version)); } /** @@ -82,6 +88,9 @@ public LocalStackContainer(final DockerImageName dockerImageName) { */ public LocalStackContainer(final DockerImageName dockerImageName, boolean useLegacyMode) { super(dockerImageName); + + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + this.legacyMode = useLegacyMode; withFileSystemBind("//var/run/docker.sock", "/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..133d485bcd5 100644 --- a/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java +++ b/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java @@ -26,15 +26,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; - /** * Default port for the binary Bolt protocol. */ @@ -59,31 +57,36 @@ public class Neo4jContainer> extends GenericContaine private String adminPassword = DEFAULT_ADMIN_PASSWORD; - private boolean standardImage = false; + private boolean standardImage = true; /** - * 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); + 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 +99,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 +148,7 @@ 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())); - } - - setDockerImageName(DOCKER_IMAGE_NAME + "-enterprise"); + setDockerImageName(getDockerImageName() + "-enterprise"); LicenseAcceptance.assertLicenseAccepted(getDockerImageName()); addEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes"); diff --git a/modules/neo4j/src/test/resources/.gitignore b/modules/neo4j/src/test/resources/.gitignore new file mode 100644 index 00000000000..486b6e376b8 --- /dev/null +++ b/modules/neo4j/src/test/resources/.gitignore @@ -0,0 +1 @@ +container-license-acceptance.txt 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..29ae1c6887f 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)); } /** @@ -32,11 +33,14 @@ public PulsarContainer() { */ @Deprecated public PulsarContainer(String pulsarVersion) { - this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(pulsarVersion)); + this(DEFAULT_IMAGE_NAME.withTag(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..c83715e6995 100644 --- a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java +++ b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java @@ -44,8 +44,8 @@ */ 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 String DEFAULT_PASSWORD = "secret"; private static final int SELENIUM_PORT = 4444; @@ -56,7 +56,7 @@ public class BrowserWebDriverContainer results = new WebDriverWait(driver, 15) - .until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.cssSelector("#search h3"))); + List results = driver.findElements(By.cssSelector("#search h3")); assertTrue("the word 'testcontainers' appears in search results", results.stream() - .anyMatch(el -> el.getText().contains("testcontainers"))); + .anyMatch(el -> el.getText().toLowerCase().contains("testcontainers"))); } protected void assertBrowserNameIs(BrowserWebDriverContainer rule, String expectedName) { diff --git a/modules/selenium/src/test/java/org/testcontainers/junit/ChromeWebDriverContainerTest.java b/modules/selenium/src/test/java/org/testcontainers/junit/ChromeWebDriverContainerTest.java index 92ba4e2cd37..6edb1e7c242 100644 --- a/modules/selenium/src/test/java/org/testcontainers/junit/ChromeWebDriverContainerTest.java +++ b/modules/selenium/src/test/java/org/testcontainers/junit/ChromeWebDriverContainerTest.java @@ -13,7 +13,7 @@ public class ChromeWebDriverContainerTest extends BaseWebDriverContainerTest { // junitRule { @Rule - public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() + public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer<>() .withCapabilities(new ChromeOptions()); // } diff --git a/modules/selenium/src/test/java/org/testcontainers/junit/Selenium3xTest.java b/modules/selenium/src/test/java/org/testcontainers/junit/Selenium3xTest.java index bbcd8c15166..1c7d3fe494d 100644 --- a/modules/selenium/src/test/java/org/testcontainers/junit/Selenium3xTest.java +++ b/modules/selenium/src/test/java/org/testcontainers/junit/Selenium3xTest.java @@ -23,7 +23,9 @@ public static String[] data() { @Test public void testAdditionalStartupString() { - final DockerImageName imageName = DockerImageName.parse("selenium/standalone-chrome-debug").withTag(tag); + final DockerImageName imageName = DockerImageName + .parse("selenium/standalone-chrome-debug") + .withTag(tag); try (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 6ecf0899ede..67b46527f27 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,9 @@ 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") + .asCompatibleSubstituteFor("selenium/standalone-firefox-debug"); @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..1906c930a95 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,7 +36,7 @@ public class SolrContainer extends GenericContainer { */ @Deprecated public SolrContainer() { - this(IMAGE + ":" + DEFAULT_TAG); + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } /** @@ -44,6 +49,9 @@ public SolrContainer(final String 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)); From 49c2360fd8bd145fdb5e684467b2926171a539bf Mon Sep 17 00:00:00 2001 From: Richard North Date: Fri, 14 Aug 2020 16:06:01 +0100 Subject: [PATCH 02/25] Revert default image name selection in Kafka/Pulsar containers --- .../main/java/org/testcontainers/containers/KafkaContainer.java | 2 +- .../java/org/testcontainers/containers/PulsarContainer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java index 9a2b0c6ed4c..c5d8b5b1250 100644 --- a/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java +++ b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java @@ -46,7 +46,7 @@ public KafkaContainer() { */ @Deprecated public KafkaContainer(String confluentPlatformVersion) { - this(DEFAULT_IMAGE_NAME.withTag(confluentPlatformVersion)); + this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(confluentPlatformVersion)); } public KafkaContainer(final DockerImageName dockerImageName) { 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 29ae1c6887f..bceff8927d7 100644 --- a/modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java +++ b/modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java @@ -33,7 +33,7 @@ public PulsarContainer() { */ @Deprecated public PulsarContainer(String pulsarVersion) { - this(DEFAULT_IMAGE_NAME.withTag(pulsarVersion)); + this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(pulsarVersion)); } public PulsarContainer(final DockerImageName dockerImageName) { From a0ef1fe186dc5e9c93bd8f75ea6dd36d0350cb13 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:30:01 +0100 Subject: [PATCH 03/25] Undeprecate remaining String constructors --- .../java/org/testcontainers/containers/MongoDbContainer.java | 1 - .../org/testcontainers/containers/JdbcDatabaseContainer.java | 1 - .../main/java/org/testcontainers/containers/SolrContainer.java | 1 - 3 files changed, 3 deletions(-) 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/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 Date: Sat, 15 Aug 2020 16:30:31 +0100 Subject: [PATCH 04/25] Add comment re validation of HostAndPort --- .../main/java/org/testcontainers/utility/DockerImageName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index cd4c7a36f31..5a45c3f0644 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -163,7 +163,7 @@ public String toString() { */ public void assertValid() { //noinspection UnstableApiUsage - HostAndPort.fromString(registry); + 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 + ")"); } From ddf8a28018a82af72c362f3f3f3ae1a203f8cd95 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:34:50 +0100 Subject: [PATCH 05/25] Add missing annotation --- .../java/org/testcontainers/containers/CassandraContainer.java | 1 + 1 file changed, 1 insertion(+) 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 ded2a1176af..597b1c9b017 100644 --- a/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java +++ b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java @@ -43,6 +43,7 @@ public class CassandraContainer> extends G /** * @deprecated use {@link #CassandraContainer(DockerImageName)} instead */ + @Deprecated public CassandraContainer() { this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } From c5c1e74adb89ff04c21aa66351c4c160a912b21b Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:43:33 +0100 Subject: [PATCH 06/25] Fix Kafka version string constructor Fix gap in testing and docs --- docs/modules/kafka.md | 4 ++-- .../containers/KafkaContainer.java | 2 +- .../containers/KafkaContainerTest.java | 24 ++++++++++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 35ac37b6906..84e5b7e7ab6 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/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java index c5d8b5b1250..4086cd4e2e4 100644 --- a/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java +++ b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java @@ -46,7 +46,7 @@ public KafkaContainer() { */ @Deprecated public KafkaContainer(String confluentPlatformVersion) { - this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(confluentPlatformVersion)); + this(TestcontainersConfiguration.getInstance().getKafkaDockerImageName().withTag(confluentPlatformVersion)); } public KafkaContainer(final DockerImageName dockerImageName) { 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 b8cd5c9be9c..0797a730df4 100644 --- a/modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java +++ b/modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java @@ -10,7 +10,7 @@ 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; @@ -29,8 +29,8 @@ public class KafkaContainerTest { 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 +43,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 +58,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 +86,8 @@ public void testExternalZookeeperWithExternalNetwork() throws Exception { .withEnv("ZOOKEEPER_CLIENT_PORT", "2181"); // withKafkaNetwork { - GenericContainer application = new GenericContainer("alpine").withNetwork(kafka.getNetwork()) + GenericContainer application = new GenericContainer("alpine") + .withNetwork(kafka.getNetwork()) // } .withNetworkAliases("dummy") .withCommand("sleep 10000") From 7d82db9611b4f590e9ec58b921e6469972656864 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:44:46 +0100 Subject: [PATCH 07/25] Use @EqualsAndHashCode for Versioning --- .../main/java/org/testcontainers/utility/Versioning.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/testcontainers/utility/Versioning.java b/core/src/main/java/org/testcontainers/utility/Versioning.java index cfa63d13fae..aba0bf90052 100644 --- a/core/src/main/java/org/testcontainers/utility/Versioning.java +++ b/core/src/main/java/org/testcontainers/utility/Versioning.java @@ -1,6 +1,8 @@ package org.testcontainers.utility; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Value; /** * Represents mechanisms for versioning docker images. @@ -10,7 +12,7 @@ interface Versioning { String getSeparator(); - @Data + @EqualsAndHashCode class TagVersioning implements Versioning { public static final String TAG_REGEX = "[\\w][\\w.\\-]{0,127}"; private final String tag; @@ -37,7 +39,7 @@ public String toString() { static final TagVersioning LATEST = new TagVersioning("latest"); } - @Data + @EqualsAndHashCode class Sha256Versioning implements Versioning { public static final String HASH_REGEX = "[0-9a-fA-F]{32,}"; private final String hash; From de6dec674d6b7a5638b6fa08a1ca61675fd9eb94 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:51:36 +0100 Subject: [PATCH 08/25] Clarify and expand test --- .../utility/DockerImageNameCompatibilityTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java index 3f33ae0b37e..12a93c72046 100644 --- a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -23,7 +23,16 @@ public void testPlainImage() { @Test public void testLatestTreatedAsWildcard() { 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"))); + assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:latest"))); assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:1.2.3").withTag("latest"))); } From d87bcee21822e98cc67297540fe5a9884727ac04 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:51:57 +0100 Subject: [PATCH 09/25] Remove duplicate test --- .../utility/DockerImageNameCompatibilityTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java index 12a93c72046..ba6baef6e5a 100644 --- a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -36,13 +36,6 @@ public void testLatestTreatedAsWildcard() { assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:1.2.3").withTag("latest"))); } - @Test - public void testImageWithAutomaticCompatibility() { - DockerImageName subject = DockerImageName.parse("foo:1.2.3"); - - assertTrue("foo:1.2.3 ~= foo", subject.isCompatibleWith(DockerImageName.parse("foo"))); - } - @Test public void testImageWithAutomaticCompatibilityForFullPath() { DockerImageName subject = DockerImageName.parse("repo/foo:1.2.3"); From 6b63e8819f4a4aaa8df6dd004534d4588e5ac818 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 16:52:22 +0100 Subject: [PATCH 10/25] Rename test --- .../utility/DockerImageNameCompatibilityTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java index ba6baef6e5a..661ffc8c18d 100644 --- a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -86,7 +86,7 @@ public void testCheckMethodAcceptsCompatible() { } @Test - public void testCheckMethodRejectsIncompatible() { + public void testAssertMethodRejectsIncompatible() { thrown.expect(IllegalStateException.class); thrown.expectMessage(containsString("Failed to verify that image 'foo' is a compatible substitute for 'bar'")); From 202a163c5c696d0abe5354190a428fd09da17073 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sat, 15 Aug 2020 17:33:35 +0100 Subject: [PATCH 11/25] Add continue-on-error for cache step, and upgrade GH cache action --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 668dde2b28c..2b5a37a3405 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,8 @@ jobs: with: java-version: '1.8' - name: Cache Gradle Home files - uses: actions/cache@v1 + uses: actions/cache@v2 + continue-on-error: true with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-home-${{matrix.gradle_args}}_check-${{ hashFiles('**/*.gradle') }} From b6e5191028f2a24504f13717ef042e79888e8911 Mon Sep 17 00:00:00 2001 From: Richard North Date: Thu, 20 Aug 2020 08:28:36 +0100 Subject: [PATCH 12/25] Update core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java Co-authored-by: Kevin Wittek --- .../utility/DockerImageNameCompatibilityTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java index 661ffc8c18d..35460586eb3 100644 --- a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -80,7 +80,7 @@ public void testImageWithClaimedCompatibilityForVersion() { } @Test - public void testCheckMethodAcceptsCompatible() { + public void testAssertMethodAcceptsCompatible() { DockerImageName subject = DockerImageName.parse("foo").asCompatibleSubstituteFor("bar"); subject.assertCompatibleWith(DockerImageName.parse("bar")); } From ee5eae3c7723c0d2c6ec4b67c742da6e5e81adff Mon Sep 17 00:00:00 2001 From: Richard North Date: Thu, 20 Aug 2020 08:50:11 +0100 Subject: [PATCH 13/25] Merge from origin/master and incorporate trim() to fix accidental whitespace in property files --- .../org/testcontainers/utility/TestcontainersConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 653e0e7a9af..c0e540984a7 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -60,7 +60,7 @@ static AtomicReference getInstanceField() { private DockerImageName getImage(final String key, final String defaultValue) { return DockerImageName - .parse(properties.getProperty(key, defaultValue)) + .parse(properties.getProperty(key, defaultValue).trim()) .asCompatibleSubstituteFor(defaultValue); } From 7a8cd7c7ff44b22ac959a776b48692eb5dc27535 Mon Sep 17 00:00:00 2001 From: Richard North Date: Wed, 26 Aug 2020 10:00:12 +0100 Subject: [PATCH 14/25] Resolve some review comments --- .../utility/DockerImageName.java | 56 ++++++++++++------- .../elasticsearch/ElasticsearchContainer.java | 14 ++--- .../ElasticsearchContainerTest.java | 16 +++--- .../containers/BrowserWebDriverContainer.java | 33 ++++++----- ...ecificImageNameWebDriverContainerTest.java | 3 +- 5 files changed, 71 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index 5a45c3f0644..bb9cdf2e1ef 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -2,12 +2,11 @@ import com.google.common.net.HostAndPort; +import java.util.regex.Pattern; import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.regex.Pattern; - @EqualsAndHashCode(exclude = "rawName") public final class DockerImageName { @@ -20,6 +19,7 @@ public final class DockerImageName { private final String rawName; private final String registry; private final String repo; + @Nullable private final Versioning versioning; @Nullable private final DockerImageName compatibleSubstituteFor; @@ -237,25 +237,43 @@ public boolean isCompatibleWith(DockerImageName other) { } /** - * Behaves as {@link DockerImageName#isCompatibleWith(DockerImageName)} but throws an exception rather than - * returning false if a mismatch is detected. + * Behaves as {@link DockerImageName#isCompatibleWith(DockerImageName)} but throws an exception + * rather than returning false if a mismatch is detected. * - * @param other the other image that we are trying to check compatibility with - * @throws IllegalStateException if {@link DockerImageName#isCompatibleWith(DockerImageName)} returns false + * @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 other) { - if (!this.isCompatibleWith(other)) { - 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, other.rawName, this.rawName, other.rawName - ) - ); + public void assertCompatibleWith(DockerImageName... anyOthers) { + if (anyOthers.length == 0) { + throw new IllegalArgumentException("anyOthers parameter must be non-empty"); } + + for (DockerImageName anyOther : anyOthers) { + if (this.isCompatibleWith(anyOther)) { + return; + } + } + + 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/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java b/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java index a1bb6d72aee..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 @@ -31,6 +30,7 @@ public class ElasticsearchContainer extends GenericContainer @@ -46,6 +45,12 @@ public class BrowserWebDriverContainer firefox = new BrowserWebDriverContainer<>(FIREFOX_IMAGE) From ad797a590d1907c98db362a8d23d9ad2e647c715 Mon Sep 17 00:00:00 2001 From: Richard North Date: Wed, 26 Aug 2020 10:10:25 +0100 Subject: [PATCH 15/25] Avoid dirty state in Kafka test --- .../containers/KafkaContainerTest.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) 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 0797a730df4..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; @@ -15,14 +22,6 @@ 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"); @@ -86,8 +85,8 @@ public void testExternalZookeeperWithExternalNetwork() throws Exception { .withEnv("ZOOKEEPER_CLIENT_PORT", "2181"); // withKafkaNetwork { - GenericContainer application = new GenericContainer("alpine") - .withNetwork(kafka.getNetwork()) + GenericContainer application = new GenericContainer<>(DockerImageName.parse("alpine")) + .withNetwork(network) // } .withNetworkAliases("dummy") .withCommand("sleep 10000") @@ -121,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(); From 1ee4b78e234702d11d4eca43b89bb0a142218aaa Mon Sep 17 00:00:00 2001 From: Richard North Date: Wed, 26 Aug 2020 10:13:36 +0100 Subject: [PATCH 16/25] Tidy up some nits --- .../testcontainers/utility/TestcontainersConfiguration.java | 2 +- .../src/main/java/org/testcontainers/utility/Versioning.java | 5 +---- .../containers/localstack/LocalStackContainer.java | 2 +- modules/neo4j/src/test/resources/.gitignore | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 modules/neo4j/src/test/resources/.gitignore diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index c0e540984a7..521a7519c2a 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -37,7 +37,7 @@ public class TestcontainersConfiguration { private static File ENVIRONMENT_CONFIG_FILE = new File(System.getProperty("user.home"), "." + PROPERTIES_FILE_NAME); @Getter(lazy = true) - private static final TestcontainersConfiguration instance = loadConfiguration();; + private static final TestcontainersConfiguration instance = loadConfiguration(); @SuppressWarnings({"ConstantConditions", "unchecked", "rawtypes"}) @VisibleForTesting diff --git a/core/src/main/java/org/testcontainers/utility/Versioning.java b/core/src/main/java/org/testcontainers/utility/Versioning.java index aba0bf90052..6639f8b20e0 100644 --- a/core/src/main/java/org/testcontainers/utility/Versioning.java +++ b/core/src/main/java/org/testcontainers/utility/Versioning.java @@ -1,8 +1,6 @@ package org.testcontainers.utility; -import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Value; /** * Represents mechanisms for versioning docker images. @@ -15,6 +13,7 @@ interface Versioning { @EqualsAndHashCode class TagVersioning implements Versioning { public static final String TAG_REGEX = "[\\w][\\w.\\-]{0,127}"; + static final TagVersioning LATEST = new TagVersioning("latest"); private final String tag; TagVersioning(String tag) { @@ -35,8 +34,6 @@ public String getSeparator() { public String toString() { return tag; } - - static final TagVersioning LATEST = new TagVersioning("latest"); } @EqualsAndHashCode 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 aa00d914832..2518cc9d041 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 @@ -72,7 +72,7 @@ public LocalStackContainer() { */ @Deprecated public LocalStackContainer(String version) { - this( TestcontainersConfiguration.getInstance().getLocalstackDockerImageName().withTag(version)); + this(TestcontainersConfiguration.getInstance().getLocalstackDockerImageName().withTag(version)); } /** diff --git a/modules/neo4j/src/test/resources/.gitignore b/modules/neo4j/src/test/resources/.gitignore deleted file mode 100644 index 486b6e376b8..00000000000 --- a/modules/neo4j/src/test/resources/.gitignore +++ /dev/null @@ -1 +0,0 @@ -container-license-acceptance.txt From 0b0212d30092b5c962b4b2624b82e6eb02fb1147 Mon Sep 17 00:00:00 2001 From: Richard North Date: Wed, 26 Aug 2020 14:07:24 +0100 Subject: [PATCH 17/25] Use Lombok @With and restore generated constructor --- build.gradle | 2 +- .../utility/DockerImageName.java | 27 +++++++------------ 2 files changed, 10 insertions(+), 19 deletions(-) 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/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index bb9cdf2e1ef..0ac00deeb73 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -3,11 +3,15 @@ import com.google.common.net.HostAndPort; import java.util.regex.Pattern; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; +import lombok.With; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @EqualsAndHashCode(exclude = "rawName") +@AllArgsConstructor(access = AccessLevel.PRIVATE) public final class DockerImageName { /* Regex patterns used for validation */ @@ -19,9 +23,9 @@ public final class DockerImageName { private final String rawName; private final String registry; private final String repo; - @Nullable + @Nullable @With(AccessLevel.PRIVATE) private final Versioning versioning; - @Nullable + @Nullable @With(AccessLevel.PRIVATE) private final DockerImageName compatibleSubstituteFor; /** @@ -113,19 +117,6 @@ public DockerImageName(String nameWithoutTag, @NotNull String version) { compatibleSubstituteFor = null; } - private DockerImageName(String rawName, - String registry, - String repo, - @Nullable Versioning versioning, - @Nullable DockerImageName compatibleSubstituteFor) { - - this.rawName = rawName; - this.registry = registry; - this.repo = repo; - this.versioning = versioning; - this.compatibleSubstituteFor = compatibleSubstituteFor; - } - /** * @return the unversioned (non 'tag') part of this name */ @@ -181,7 +172,7 @@ public String getRegistry() { * @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 Versioning.TagVersioning(newTag), compatibleSubstituteFor); + return withVersioning(new Versioning.TagVersioning(newTag)); } /** @@ -192,7 +183,7 @@ public DockerImageName withTag(final String newTag) { * @return an immutable copy of this {@link DockerImageName} with the compatibility declaration attached. */ public DockerImageName asCompatibleSubstituteFor(String otherImageName) { - return asCompatibleSubstituteFor(DockerImageName.parse(otherImageName)); + return withCompatibleSubstituteFor(DockerImageName.parse(otherImageName)); } /** @@ -203,7 +194,7 @@ public DockerImageName asCompatibleSubstituteFor(String otherImageName) { * @return an immutable copy of this {@link DockerImageName} with the compatibility declaration attached. */ public DockerImageName asCompatibleSubstituteFor(DockerImageName otherImageName) { - return new DockerImageName(rawName, registry, repo, versioning, otherImageName); + return withCompatibleSubstituteFor(otherImageName); } /** From e22e639fbf740c326d1fd492a0e1c1ae124a8c53 Mon Sep 17 00:00:00 2001 From: Richard North Date: Wed, 26 Aug 2020 14:07:49 +0100 Subject: [PATCH 18/25] Remove now-unused `TestcontainersConfiguration` accessors --- .../containers/GenericContainer.java | 76 +++++++++---------- .../utility/TestcontainersConfiguration.java | 50 ------------ 2 files changed, 37 insertions(+), 89 deletions(-) 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/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 521a7519c2a..326938bc180 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -64,48 +64,23 @@ private DockerImageName getImage(final String key, final String defaultValue) { .asCompatibleSubstituteFor(defaultValue); } - @Deprecated - public String getAmbassadorContainerImage() { - return getAmbassadorContainerDockerImageName().asCanonicalNameString(); - } - @Deprecated public DockerImageName getAmbassadorContainerDockerImageName() { return getImage("ambassador.container.image", "richnorth/ambassador:latest"); } - @Deprecated - public String getSocatContainerImage() { - return getSocatDockerImageName().asCanonicalNameString(); - } - public DockerImageName getSocatDockerImageName() { return getImage("socat.container.image", "alpine/socat:latest"); } - @Deprecated - public String getVncRecordedContainerImage() { - return getVncDockerImageName().asCanonicalNameString(); - } - public DockerImageName getVncDockerImageName() { return getImage("vncrecorder.container.image", "testcontainers/vnc-recorder:1.1.0"); } - @Deprecated - public String getDockerComposeContainerImage() { - return getDockerComposeDockerImageName().asCanonicalNameString(); - } - public DockerImageName getDockerComposeDockerImageName() { return getImage("compose.container.image", "docker/compose:1.24.1"); } - @Deprecated - public String getTinyImage() { - return getTinyDockerImageName().asCanonicalNameString(); - } - public DockerImageName getTinyDockerImageName() { return getImage("tinyimage.container.image", "alpine:3.5"); } @@ -114,20 +89,10 @@ public boolean isRyukPrivileged() { return Boolean.parseBoolean((String) properties.getOrDefault("ryuk.container.privileged", "false")); } - @Deprecated - public String getRyukImage() { - return getRyukDockerImageName().asCanonicalNameString(); - } - public DockerImageName getRyukDockerImageName() { return getImage("ryuk.container.image", "testcontainers/ryuk:0.3.0"); } - @Deprecated - public String getSSHdImage() { - return getSSHdDockerImageName().asCanonicalNameString(); - } - public DockerImageName getSSHdDockerImageName() { return getImage("sshd.container.image", "testcontainers/sshd:1.0.0"); } @@ -136,29 +101,14 @@ public Integer getRyukTimeout() { return Integer.parseInt((String) properties.getOrDefault("ryuk.container.timeout", "30")); } - @Deprecated - public String getKafkaImage() { - return getKafkaDockerImageName().asCanonicalNameString(); - } - public DockerImageName getKafkaDockerImageName() { return getImage("kafka.container.image", "confluentinc/cp-kafka"); } - @Deprecated - public String getPulsarImage() { - return getPulsarDockerImageName().asCanonicalNameString(); - } - public DockerImageName getPulsarDockerImageName() { return getImage("pulsar.container.image", "apachepulsar/pulsar"); } - @Deprecated - public String getLocalStackImage() { - return getLocalstackDockerImageName().asCanonicalNameString(); - } - public DockerImageName getLocalstackDockerImageName() { return getImage("localstack.container.image", "localstack/localstack"); } From 90688338ec52a70a2919a72ce12dd4e7885e90de Mon Sep 17 00:00:00 2001 From: Richard North Date: Wed, 26 Aug 2020 15:13:51 +0100 Subject: [PATCH 19/25] Fix test compilation issue --- .../utility/TestcontainersConfigurationTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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()); } From 64108f1daf18d8f4f3e6f3f4087e3d95a050750a Mon Sep 17 00:00:00 2001 From: Richard North Date: Thu, 27 Aug 2020 08:03:37 +0100 Subject: [PATCH 20/25] Revert "Remove now-unused `TestcontainersConfiguration` accessors" e22e639fbf740c326d1fd492a0e1c1ae124a8c53 --- .../utility/TestcontainersConfiguration.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 326938bc180..521a7519c2a 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -64,23 +64,48 @@ private DockerImageName getImage(final String key, final String defaultValue) { .asCompatibleSubstituteFor(defaultValue); } + @Deprecated + public String getAmbassadorContainerImage() { + return getAmbassadorContainerDockerImageName().asCanonicalNameString(); + } + @Deprecated public DockerImageName getAmbassadorContainerDockerImageName() { return getImage("ambassador.container.image", "richnorth/ambassador:latest"); } + @Deprecated + public String getSocatContainerImage() { + return getSocatDockerImageName().asCanonicalNameString(); + } + public DockerImageName getSocatDockerImageName() { return getImage("socat.container.image", "alpine/socat:latest"); } + @Deprecated + public String getVncRecordedContainerImage() { + return getVncDockerImageName().asCanonicalNameString(); + } + public DockerImageName getVncDockerImageName() { return getImage("vncrecorder.container.image", "testcontainers/vnc-recorder:1.1.0"); } + @Deprecated + public String getDockerComposeContainerImage() { + return getDockerComposeDockerImageName().asCanonicalNameString(); + } + public DockerImageName getDockerComposeDockerImageName() { return getImage("compose.container.image", "docker/compose:1.24.1"); } + @Deprecated + public String getTinyImage() { + return getTinyDockerImageName().asCanonicalNameString(); + } + public DockerImageName getTinyDockerImageName() { return getImage("tinyimage.container.image", "alpine:3.5"); } @@ -89,10 +114,20 @@ public boolean isRyukPrivileged() { return Boolean.parseBoolean((String) properties.getOrDefault("ryuk.container.privileged", "false")); } + @Deprecated + public String getRyukImage() { + return getRyukDockerImageName().asCanonicalNameString(); + } + public DockerImageName getRyukDockerImageName() { return getImage("ryuk.container.image", "testcontainers/ryuk:0.3.0"); } + @Deprecated + public String getSSHdImage() { + return getSSHdDockerImageName().asCanonicalNameString(); + } + public DockerImageName getSSHdDockerImageName() { return getImage("sshd.container.image", "testcontainers/sshd:1.0.0"); } @@ -101,14 +136,29 @@ public Integer getRyukTimeout() { return Integer.parseInt((String) properties.getOrDefault("ryuk.container.timeout", "30")); } + @Deprecated + public String getKafkaImage() { + return getKafkaDockerImageName().asCanonicalNameString(); + } + public DockerImageName getKafkaDockerImageName() { return getImage("kafka.container.image", "confluentinc/cp-kafka"); } + @Deprecated + public String getPulsarImage() { + return getPulsarDockerImageName().asCanonicalNameString(); + } + public DockerImageName getPulsarDockerImageName() { return getImage("pulsar.container.image", "apachepulsar/pulsar"); } + @Deprecated + public String getLocalStackImage() { + return getLocalstackDockerImageName().asCanonicalNameString(); + } + public DockerImageName getLocalstackDockerImageName() { return getImage("localstack.container.image", "localstack/localstack"); } From dfa1e635931b62088b47f67e5778490668256846 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sun, 6 Sep 2020 17:00:16 +0100 Subject: [PATCH 21/25] Add an 'AnyVersion' for images --- .../utility/DockerImageName.java | 25 ++++++-------- .../testcontainers/utility/Versioning.java | 34 ++++++++++++++++++- .../utility/DockerImageNameTest.java | 4 +-- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index 0ac00deeb73..7506582766c 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -2,7 +2,6 @@ import com.google.common.net.HostAndPort; -import java.util.regex.Pattern; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -10,7 +9,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -@EqualsAndHashCode(exclude = "rawName") +import java.util.regex.Pattern; + +@EqualsAndHashCode(exclude = { "rawName", "compatibleSubstituteFor" }) @AllArgsConstructor(access = AccessLevel.PRIVATE) public final class DockerImageName { @@ -23,7 +24,7 @@ public final class DockerImageName { private final String rawName; private final String registry; private final String repo; - @Nullable @With(AccessLevel.PRIVATE) + @NotNull @With(AccessLevel.PRIVATE) private final Versioning versioning; @Nullable @With(AccessLevel.PRIVATE) private final DockerImageName compatibleSubstituteFor; @@ -72,7 +73,7 @@ public DockerImageName(String fullImageName) { versioning = new Versioning.TagVersioning(remoteName.split(":")[1]); } else { repo = remoteName; - versioning = Versioning.TagVersioning.LATEST; + versioning = Versioning.ANY; } compatibleSubstituteFor = null; @@ -132,14 +133,14 @@ public String getUnversionedPart() { * @return the versioned part of this name (tag or sha256) */ public String getVersionPart() { - return versioning == null ? "latest" : versioning.toString(); + return versioning.toString(); } /** * @return canonical name for the image */ public String asCanonicalNameString() { - return getUnversionedPart() + (versioning == null ? ":" : versioning.getSeparator()) + getVersionPart(); + return getUnversionedPart() + versioning.getSeparator() + getVersionPart(); } @Override @@ -158,7 +159,7 @@ public void assertValid() { if (!REPO_NAME.matcher(repo).matches()) { throw new IllegalArgumentException(repo + " is not a valid Docker image name (in " + rawName + ")"); } - if (versioning != null && !versioning.isValid()) { + if (!versioning.isValid()) { throw new IllegalArgumentException(versioning + " is not a valid image versioning identifier (in " + rawName + ")"); } } @@ -209,14 +210,8 @@ public DockerImageName asCompatibleSubstituteFor(DockerImageName otherImageName) * @return whether this image has declared compatibility. */ public boolean isCompatibleWith(DockerImageName other) { - // is this image already the same? - final boolean thisRegistrySame = other.registry.equals(this.registry); - final boolean thisRepoSame = other.repo.equals(this.repo); - final boolean thisVersioningNotSpecifiedOrSame = other.versioning == null || - other.versioning.equals(Versioning.TagVersioning.LATEST) || - other.versioning.equals(this.versioning); - - if (thisRegistrySame && thisRepoSame && thisVersioningNotSpecifiedOrSame) { + // is this image already the same or equivalent? + if (other.equals(this)) { return true; } diff --git a/core/src/main/java/org/testcontainers/utility/Versioning.java b/core/src/main/java/org/testcontainers/utility/Versioning.java index 6639f8b20e0..8944b3f0ba6 100644 --- a/core/src/main/java/org/testcontainers/utility/Versioning.java +++ b/core/src/main/java/org/testcontainers/utility/Versioning.java @@ -1,19 +1,51 @@ 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}"; - static final TagVersioning LATEST = new TagVersioning("latest"); private final String tag; TagVersioning(String tag) { 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()); From a5b63ffe7e50a4960de457c8c4d90d822c0f36a9 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sun, 6 Sep 2020 17:02:23 +0100 Subject: [PATCH 22/25] Add import --- .../org/testcontainers/utility/DockerImageName.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java index 7506582766c..af8e5300863 100644 --- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java +++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java @@ -8,6 +8,8 @@ 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; @@ -67,10 +69,10 @@ public DockerImageName(String fullImageName) { if (remoteName.contains("@sha256:")) { repo = remoteName.split("@sha256:")[0]; - versioning = new Versioning.Sha256Versioning(remoteName.split("@sha256:")[1]); + versioning = new Sha256Versioning(remoteName.split("@sha256:")[1]); } else if (remoteName.contains(":")) { repo = remoteName.split(":")[0]; - versioning = new Versioning.TagVersioning(remoteName.split(":")[1]); + versioning = new TagVersioning(remoteName.split(":")[1]); } else { repo = remoteName; versioning = Versioning.ANY; @@ -109,10 +111,10 @@ public DockerImageName(String nameWithoutTag, @NotNull String version) { if (version.startsWith("sha256:")) { repo = remoteName; - versioning = new Versioning.Sha256Versioning(version.replace("sha256:", "")); + versioning = new Sha256Versioning(version.replace("sha256:", "")); } else { repo = remoteName; - versioning = new Versioning.TagVersioning(version); + versioning = new TagVersioning(version); } compatibleSubstituteFor = null; @@ -173,7 +175,7 @@ public String getRegistry() { * @return an immutable copy of this {@link DockerImageName} with the new version tag */ public DockerImageName withTag(final String newTag) { - return withVersioning(new Versioning.TagVersioning(newTag)); + return withVersioning(new TagVersioning(newTag)); } /** From 3d43019550e75b379e73d0ef929b66a83c14c410 Mon Sep 17 00:00:00 2001 From: Richard North Date: Sun, 6 Sep 2020 20:58:37 +0100 Subject: [PATCH 23/25] Adapt test for mocking compatibility (avoid testing latest vs any for equality) --- .../utility/AuthenticatedImagePullTest.java | 17 ++++++++--------- .../DockerImageNameCompatibilityTest.java | 5 +---- 2 files changed, 9 insertions(+), 13 deletions(-) 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 index 35460586eb3..9e7136ea6d3 100644 --- a/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java +++ b/core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java @@ -21,7 +21,7 @@ public void testPlainImage() { assertFalse("image name foo != bar", subject.isCompatibleWith(DockerImageName.parse("bar"))); } @Test - public void testLatestTreatedAsWildcard() { + public void testNoTagTreatedAsWildcard() { final DockerImageName subject = DockerImageName.parse("foo:4.5.6"); /* foo:1.2.3 != foo:4.5.6 @@ -32,8 +32,6 @@ public void testLatestTreatedAsWildcard() { */ 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"))); - assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:latest"))); - assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:1.2.3").withTag("latest"))); } @Test @@ -73,7 +71,6 @@ public void testImageWithClaimedCompatibilityForVersion() { 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"))); - assertTrue("foo(bar:1.2.3) ~= bar:latest", subject.isCompatibleWith(DockerImageName.parse("bar:latest"))); 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"))); From 022c49338c13eeb3ba690264f708d8a1c681a50d Mon Sep 17 00:00:00 2001 From: Richard North Date: Thu, 24 Sep 2020 09:12:21 +0100 Subject: [PATCH 24/25] Restore previous behaviour using a fixed version of Neo4j Enterprise image --- .../containers/Neo4jContainer.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) 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 133d485bcd5..f05984c0c85 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. * @@ -32,6 +31,7 @@ public class Neo4jContainer> extends GenericContaine * The default tag (version) to use. */ private static final String DEFAULT_TAG = "3.5.0"; + private static final String ENTERPRISE_TAG = DEFAULT_TAG + "-enterprise"; /** * Default port for the binary Bolt protocol. @@ -57,8 +57,6 @@ public class Neo4jContainer> extends GenericContaine private String adminPassword = DEFAULT_ADMIN_PASSWORD; - private boolean standardImage = true; - /** * Creates a Neo4jContainer using the official Neo4j docker image. * @deprecated use {@link Neo4jContainer(DockerImageName)} instead @@ -148,7 +146,7 @@ public String getHttpsUrl() { * @return This container. */ public S withEnterpriseEdition() { - setDockerImageName(getDockerImageName() + "-enterprise"); + setDockerImageName(DEFAULT_IMAGE_NAME.withTag(ENTERPRISE_TAG).asCanonicalNameString()); LicenseAcceptance.assertLicenseAccepted(getDockerImageName()); addEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes"); From 6cc5c5f189db27ef52ca8d575a15bcd0e3ba0d71 Mon Sep 17 00:00:00 2001 From: Richard North Date: Thu, 24 Sep 2020 09:22:11 +0100 Subject: [PATCH 25/25] Reinstate standard image check --- .../org/testcontainers/containers/Neo4jContainer.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 f05984c0c85..e93a840302a 100644 --- a/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java +++ b/modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java @@ -55,6 +55,8 @@ public class Neo4jContainer> extends GenericContaine private static final String AUTH_FORMAT = "neo4j/%s"; + private final boolean standardImage; + private String adminPassword = DEFAULT_ADMIN_PASSWORD; /** @@ -83,6 +85,9 @@ public Neo4jContainer(String dockerImageName) { 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() @@ -146,6 +151,12 @@ 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())); + } + setDockerImageName(DEFAULT_IMAGE_NAME.withTag(ENTERPRISE_TAG).asCanonicalNameString()); LicenseAcceptance.assertLicenseAccepted(getDockerImageName());