-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add image compatibility checks #3021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
So that compatibility assurances can be made in code rather than just being assumed.
71fe1b7 to
484f3cd
Compare
|
|
||
| @Override | ||
| public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) { | ||
| // TODO work out how best to do this if these constants become private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A TODO for a point in the near future. This has a lot to do with mandatory bring-your-own-image in R2DBC and JDBC URLs as discussed in Slack (@bsideup)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I missed the Slack discussion, but just being pragmatic and make the constants packacke-private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's probably going to be the answer. This isn't something to worry about too much for now, anyway.
kiview
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR looks good overall, just had some questions and suggestion.
One copy-paste error with KafkaContainer it seems ;)
| /** | ||
| * @deprecated use {@link MongoDBContainer(DockerImageName)} instead | ||
| */ | ||
| @Deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really un-deprecate the String constructors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this - will look for others.
| * @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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we would return DockerImageName, we could use this method in super constructor arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd push back against this - it feels a bit strange to have a value be passed through a method that does assertion. I think I like the assertion being a distinct line in each constructor (after the call to super), as it feels more visible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JDK likes to object this argument of feeling strange 🙂
https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html#requireNonNull-T-
But this is not a hill I need to die on. I like it in super constructor though, because this means it gets evaluated before the super constructor is called.
| } | ||
|
|
||
| public MongoDBContainer(final DockerImageName dockerImageName) { | ||
| super(dockerImageName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If assertCompatibleWith would return DockerImageName, we coould use it as argument for the super constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above.
| /** | ||
| * @deprecated use {@link #CassandraContainer(DockerImageName)} instead | ||
| */ | ||
| @Deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By which logic are the deprecations of constructors removed now? Seems kind of inconsistent between classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were missing the deprecated annotation on CassandraContainer's no-arg constructor 🤦
The logic should be:
- No-arg constructors: always deprecated
- String, image name constructors: not deprecated
- String, version constructors: always deprecated
| @Deprecated | ||
| public KafkaContainer(String confluentPlatformVersion) { | ||
| this(DockerImageName.parse(TestcontainersConfiguration.getInstance().getKafkaImage() + ":" + confluentPlatformVersion)); | ||
| this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(confluentPlatformVersion)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| this(TestcontainersConfiguration.getInstance().getPulsarDockerImageName().withTag(confluentPlatformVersion)); | |
| this(TestcontainersConfiguration.getInstance().getKafkaDockerImageName().withTag(confluentPlatformVersion)); |
Is this constructor missing a test therefore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot - silly c&p error 😬
Yes, this is missing test coverage. Will add.
|
|
||
| String getSeparator(); | ||
|
|
||
| @Data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Value instead of @Data? Or @EqualsAndHashcode? Or we don't use lombok in the first plance, since we already define toString() and the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@EqualsAndHashCode would do the trick - good suggestion.
| 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"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo:1.2.3").withTag("latest"))); | |
| assertTrue("foo:4.5.6 ~= foo:latest", subject.isCompatibleWith(DockerImageName.parse("foo"))); |
Since latest is default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, the intent here is to make sure that setting latest tag doesn't mess things up. I'll add a clarifying comment:
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
| } | ||
|
|
||
| @Test | ||
| public void testImageWithAutomaticCompatibility() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after my suggestion, this would be the same test as testLatestTreatedAsWildcard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| @Test | ||
| public void testCheckMethodRejectsIncompatible() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public void testCheckMethodRejectsIncompatible() { | |
| public void testAssertMethodRejectsIncompatible() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Fix gap in testing and docs
…ompatibilityTest.java Co-authored-by: Kevin Wittek <[email protected]>
Adapt trim() usage to new code structure
…tespace in property files
| if (remoteName.contains("@sha256:")) { | ||
| repo = remoteName.split("@sha256:")[0]; | ||
| versioning = new Sha256Versioning(remoteName.split("@sha256:")[1]); | ||
| versioning = new Versioning.Sha256Versioning(remoteName.split("@sha256:")[1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny nit: if we import Versioning.Sha256Versioning and other Versioning.* classes, the changelog should be smaller :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rnorth is it? 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, must have reverted it during moving the interface. Done again.
| private DockerImageName(String rawName, | ||
| String registry, | ||
| String repo, | ||
| @Nullable Versioning versioning, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
marked as @Nullable while the field isn't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| */ | ||
| public String getVersionPart() { | ||
| return versioning.toString(); | ||
| return versioning == null ? "latest" : versioning.toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make versioning @NonNull and use Versioning.TagVersioning.LATEST if null is passed to @Nullable methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is one that requires some discussion, and potentially a change or just clear docs!
I'd expect most usage of this feature to be like foo.asCompatibleSubstituteFor("bar") meaning bar with any tag is accepted.
I wanted to leave the possibility open to specify an exact tag match, i.e. foo.asCompatibleSubstituteFor("bar:1.2.3").
So that this works:
- an absent tag is recorded as
null - the compatitility check code treats this
nullas a wildcard - conversion to a string treats this
nullas an implicitlatest
It doesn't have to be this way, though. I reckon we could:
- ignore tags altogether for compatibility checks
- OR be more explicit about wildcards, e.g.
foo.asCompatibleSubstituteFor("bar:*")in the API, and/or have aVersioning.Wildcardtype internally instead ofnull.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have a
Versioning.Wildcardtype internally instead ofnull
I like this one! My main concern was the Nullable field that we can avoid and I think Versioning.Wildcard solves the problem very well 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 cool, I'll go with that then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (called it AnyVersion)
| */ | ||
| public String asCanonicalNameString() { | ||
| return getUnversionedPart() + versioning.getSeparator() + versioning.toString(); | ||
| return getUnversionedPart() + (versioning == null ? ":" : versioning.getSeparator()) + getVersionPart(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto re null vs Versioning.TagVersioning.LATEST
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| * @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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding @With(AccessLevel.PRIVATE) to otherImageName, so that we can do:
| return new DockerImageName(rawName, registry, repo, versioning, otherImageName); | |
| return withOtherImageName(otherImageName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this works well (same for withTag).
N.B. I've upgraded the Lombok dependency so that we can use modern @With rather than deprecated @Wither
All comments responded to (either changed or I think I've explained!)
bsideup
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🚀
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
| * Elasticsearch Docker base image | ||
| */ | ||
| private static final String ELASTICSEARCH_DEFAULT_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch"; | ||
| private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we could make this static constant public so people can simply do something like:
new ElasticsearchContainer(ElasticsearchContainer.DEFAULT_IMAGE_NAME.withTag("7.9.2"));Builds upon #3021: * adds a pluggable image substitution mechanism using ServiceLoader, enabling users to perform custom substitution/auditing of images being used by their tests * provides a default implementation that behaves similarly to legacy `TestcontainersConfiguration` approach (`testcontainers.properties`), but also... * For many orgs, sticking a prefix on the front of image names might be enough to use a private registry. I've added a default behaviour whereby, if a particular environment variable is present, image names are automatically substituted. e.g. `TESTCONTAINERS_IMAGE_NAME_PREFIX=my.registry.com/` would transform `redis` to `my.registry.com/redis` etc. Notes: * behaviour is similar but not quite identical to `TestcontainersConfiguration`: use of a configured custom image for, e.g. Kafka/Pulsar that does not have a tag specified causes the substitution to take effect for all usages. It seems very unlikely that people would be using a mix of the config file image overrides in some places _and_ specific images specified in code in others. * Duplication of default image names in modules vs `TestcontainersConfiguration` class is intentional: specifying image overrides in `testcontainers.properties` should be removed in the future. * ~Add log deprecation warnings when `testcontainers.properties` image overrides are used.~ Defer to a future release?
Builds upon #3021: * adds a pluggable image substitution mechanism using ServiceLoader, enabling users to perform custom substitution/auditing of images being used by their tests * provides a default implementation that behaves similarly to legacy `TestcontainersConfiguration` approach (`testcontainers.properties`), but also... * For many orgs, sticking a prefix on the front of image names might be enough to use a private registry. I've added a default behaviour whereby, if a particular environment variable is present, image names are automatically substituted. e.g. `TESTCONTAINERS_IMAGE_NAME_PREFIX=my.registry.com/` would transform `redis` to `my.registry.com/redis` etc. Notes: * behaviour is similar but not quite identical to `TestcontainersConfiguration`: use of a configured custom image for, e.g. Kafka/Pulsar that does not have a tag specified causes the substitution to take effect for all usages. It seems very unlikely that people would be using a mix of the config file image overrides in some places _and_ specific images specified in code in others. * Duplication of default image names in modules vs `TestcontainersConfiguration` class is intentional: specifying image overrides in `testcontainers.properties` should be removed in the future. * ~Add log deprecation warnings when `testcontainers.properties` image overrides are used.~ Defer to a future release?
Builds upon #3021 and #3411: * adds a pluggable image substitution mechanism using ServiceLoader, enabling users to perform custom substitution/auditing of images being used by their tests * provides a default implementation that behaves similarly to legacy `TestcontainersConfiguration` approach (`testcontainers.properties`) Notes: * behaviour is similar but not quite identical to `TestcontainersConfiguration`: use of a configured custom image for, e.g. Kafka/Pulsar that does not have a tag specified causes the substitution to take effect for all usages. It seems very unlikely that people would be using a mix of the config file image overrides in some places _and_ specific images specified in code in others. * Duplication of default image names in modules vs `TestcontainersConfiguration` class is intentional: specifying image overrides in `testcontainers.properties` should be removed in the future. * ~Add log deprecation warnings when `testcontainers.properties` image overrides are used.~ Defer to a future release?
Builds upon #3021 and #3411: * adds a pluggable image substitution mechanism using ServiceLoader, enabling users to perform custom substitution/auditing of images being used by their tests * provides a default implementation that behaves similarly to legacy `TestcontainersConfiguration` approach (`testcontainers.properties`) Notes: * behaviour is similar but not quite identical to `TestcontainersConfiguration`: use of a configured custom image for, e.g. Kafka/Pulsar that does not have a tag specified causes the substitution to take effect for all usages. It seems very unlikely that people would be using a mix of the config file image overrides in some places _and_ specific images specified in code in others. * Duplication of default image names in modules vs `TestcontainersConfiguration` class is intentional: specifying image overrides in `testcontainers.properties` should be removed in the future. * ~Add log deprecation warnings when `testcontainers.properties` image overrides are used.~ Defer to a future release?
* Refactor Testcontainers configuration to allow config by env var * Add Image substitution mechanism Builds upon #3021 and #3411: * adds a pluggable image substitution mechanism using ServiceLoader, enabling users to perform custom substitution/auditing of images being used by their tests * provides a default implementation that behaves similarly to legacy `TestcontainersConfiguration` approach (`testcontainers.properties`) Notes: * behaviour is similar but not quite identical to `TestcontainersConfiguration`: use of a configured custom image for, e.g. Kafka/Pulsar that does not have a tag specified causes the substitution to take effect for all usages. It seems very unlikely that people would be using a mix of the config file image overrides in some places _and_ specific images specified in code in others. * Duplication of default image names in modules vs `TestcontainersConfiguration` class is intentional: specifying image overrides in `testcontainers.properties` should be removed in the future. * ~Add log deprecation warnings when `testcontainers.properties` image overrides are used.~ Defer to a future release? * Remove extraneous change * Un-ignore docs example test by implementing a 'reversing' image name substitutor * Use configuration, not service loader, to select an ImageNameSubstitutor * Add check for order of config setting precedence * Extract classpath scanner and support finding of multiple resources * Introduce deterministic merging of classpath properties files * Update docs * Update docs * Remove service loader reference * Chain substitution through default and configured implementations * Small tweaks following review * Fix test compile error * Add UnstableAPI annotation * Move TestSpecificImageNameSubstitutor back to original package and remove duplicate use of default substitutor
Background to this change: the majority of modules make assumptions about the container image being used - for example, port numbers, expected log lines, etc. When asking users to provide their own images with modules, it is potentially confusing if the provided image diverges from the original 'vendor-provided' image that the module was built to support.
This change is intended to ensure that, if the user provides their own image that is not the same as the vendor-provided one, they are given adequate warning and forced to signal that this is intentional.
For example:
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:any"))will just work, becauseconfluentinc/cp-kafkamatches the image name thatKafkaContainerwas designed to work withnew KafkaContainer(DockerImageName.parse("some-other-kafka"))will not work immediately, becausesome-other-kafkamay be an entirely divergent image fromconfluentinc/cp-kafka. In this case, the user would be prompted to add.asCompatibleSubstituteFor("confluentinc/cp-kafka")which tells Testcontainers that this is a conscious decisionThis PR:
Adds to
DockerImageName:asCompatibleSubstituteFor(DockerImageName)andasCompatibleSubstituteFor(String)methods which may be used to claim compatibility with a vendor-provided imageisCompatibleWith(DockerImageName)andassertCompatibleWith(DockerImageName)methods which can be used by Testcontainers to check that the provided image is compatible with the expected vendor-provided imageRefactors all modules to use this new mechanism
Amends the
TestcontainersConfigurationclass so that any file-based overrides automatically claim compatibility