Skip to content

Commit 6da85e7

Browse files
Preserve tag in DockerImageName when both tag and digest are present
When parsing image names like "image:7.3.0@sha256:abcd", the tag was previously discarded. This caused getVersionPart() to return the sha256 digest instead of the tag, breaking version-based feature checks in modules like Kafka, Neo4j, Elasticsearch, etc. Now getVersionPart() returns the tag when both are present, and a new getDigest() method provides access to the sha256 digest. RemoteDockerImage uses the digest for pulling when available. asCanonicalNameString() correctly outputs "image:tag@sha256:hash" format.
1 parent 6c576a5 commit 6da85e7

File tree

5 files changed

+72
-8
lines changed

5 files changed

+72
-8
lines changed

core/src/main/java/org/testcontainers/images/RemoteDockerImage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ protected final String resolve() {
8989
final Instant startedAt = Instant.now();
9090
final Instant lastRetryAllowed = Instant.now().plus(PULL_RETRY_TIME_LIMIT);
9191
final AtomicReference<Exception> lastFailure = new AtomicReference<>();
92+
String pullTag = imageName.getDigest() != null ? imageName.getDigest() : imageName.getVersionPart();
9293
final PullImageCmd pullImageCmd = dockerClient
9394
.pullImageCmd(imageName.getUnversionedPart())
94-
.withTag(imageName.getVersionPart());
95+
.withTag(pullTag);
9596
final AtomicReference<String> dockerImageName = new AtomicReference<>();
9697

9798
// The following poll interval in ms: 50, 100, 200, 400, 800....

core/src/main/java/org/testcontainers/utility/DockerImageName.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,14 @@ public DockerImageName(String fullImageName) {
8888

8989
if (remoteName.contains("@sha256:")) {
9090
String beforeDigest = remoteName.split("@sha256:")[0];
91-
repository = beforeDigest.contains(":") ? beforeDigest.split(":")[0] : beforeDigest;
92-
versioning = new Sha256Versioning(remoteName.split("@sha256:")[1]);
91+
if (beforeDigest.contains(":")) {
92+
repository = beforeDigest.split(":")[0];
93+
String tag = beforeDigest.split(":")[1];
94+
versioning = new Sha256Versioning(remoteName.split("@sha256:")[1], tag);
95+
} else {
96+
repository = beforeDigest;
97+
versioning = new Sha256Versioning(remoteName.split("@sha256:")[1]);
98+
}
9399
} else if (remoteName.contains(":")) {
94100
repository = remoteName.split(":")[0];
95101
versioning = new TagVersioning(remoteName.split(":")[1]);
@@ -156,16 +162,40 @@ public String getUnversionedPart() {
156162
}
157163

158164
/**
159-
* @return the versioned part of this name (tag or sha256)
165+
* @return the versioned part of this name (tag or sha256). When both tag and digest are present,
166+
* the tag is returned.
160167
*/
161168
public String getVersionPart() {
169+
if (versioning instanceof Sha256Versioning) {
170+
String tag = ((Sha256Versioning) versioning).getTag();
171+
if (tag != null) {
172+
return tag;
173+
}
174+
}
162175
return versioning.toString();
163176
}
164177

178+
/**
179+
* @return the sha256 digest if present, or null
180+
*/
181+
@Nullable
182+
public String getDigest() {
183+
if (versioning instanceof Sha256Versioning) {
184+
return versioning.toString();
185+
}
186+
return null;
187+
}
188+
165189
/**
166190
* @return canonical name for the image
167191
*/
168192
public String asCanonicalNameString() {
193+
if (versioning instanceof Sha256Versioning) {
194+
String tag = ((Sha256Versioning) versioning).getTag();
195+
if (tag != null) {
196+
return getUnversionedPart() + ":" + tag + "@" + versioning;
197+
}
198+
}
169199
return getUnversionedPart() + versioning.getSeparator() + getVersionPart();
170200
}
171201

core/src/main/java/org/testcontainers/utility/Versioning.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,20 @@ class Sha256Versioning implements Versioning {
7979

8080
private final String hash;
8181

82+
@EqualsAndHashCode.Exclude
83+
private final String tag;
84+
8285
Sha256Versioning(String hash) {
86+
this(hash, null);
87+
}
88+
89+
Sha256Versioning(String hash, String tag) {
8390
this.hash = hash;
91+
this.tag = tag;
92+
}
93+
94+
String getTag() {
95+
return tag;
8496
}
8597

8698
@Override

core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ void testTagAndDigestImageIsCompatible() {
105105

106106
@Test
107107
void testTagAndDigestImageWithRegistryIsCompatible() {
108-
DockerImageName subject = DockerImageName.parse("registry.foo.com/repo:tag@sha256:1234abcd1234abcd1234abcd1234abcd");
108+
DockerImageName subject = DockerImageName.parse(
109+
"registry.foo.com/repo:tag@sha256:1234abcd1234abcd1234abcd1234abcd"
110+
);
109111

110112
assertThat(subject.isCompatibleWith(DockerImageName.parse("registry.foo.com/repo")))
111113
.as("registry.foo.com/repo:tag@sha256:... ~= registry.foo.com/repo")

core/src/test/java/org/testcontainers/utility/DockerImageNameTest.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,24 @@ void testTagAndDigestStripsTagFromRepository() {
160160

161161
assertThat(imageName.getRegistry()).isEqualTo("");
162162
assertThat(imageName.getUnversionedPart()).isEqualTo("myname");
163-
assertThat(imageName.getVersionPart()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
163+
assertThat(imageName.getVersionPart()).isEqualTo("latest");
164+
assertThat(imageName.getDigest()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
165+
assertThat(imageName.asCanonicalNameString())
166+
.isEqualTo("myname:latest@sha256:1234abcd1234abcd1234abcd1234abcd");
164167
}
165168

166169
@Test
167170
void testTagAndDigestWithRepoPath() {
168-
DockerImageName imageName = DockerImageName.parse("repo/myname:1.0@sha256:1234abcd1234abcd1234abcd1234abcd");
171+
DockerImageName imageName = DockerImageName.parse(
172+
"repo/myname:1.0@sha256:1234abcd1234abcd1234abcd1234abcd"
173+
);
169174

170175
assertThat(imageName.getRegistry()).isEqualTo("");
171176
assertThat(imageName.getUnversionedPart()).isEqualTo("repo/myname");
172-
assertThat(imageName.getVersionPart()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
177+
assertThat(imageName.getVersionPart()).isEqualTo("1.0");
178+
assertThat(imageName.getDigest()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
179+
assertThat(imageName.asCanonicalNameString())
180+
.isEqualTo("repo/myname:1.0@sha256:1234abcd1234abcd1234abcd1234abcd");
173181
}
174182

175183
@Test
@@ -180,7 +188,18 @@ void testTagAndDigestWithRegistry() {
180188

181189
assertThat(imageName.getRegistry()).isEqualTo("registry.foo.com:1234");
182190
assertThat(imageName.getUnversionedPart()).isEqualTo("registry.foo.com:1234/repo-here/my-name");
191+
assertThat(imageName.getVersionPart()).isEqualTo("1.0");
192+
assertThat(imageName.getDigest()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
193+
assertThat(imageName.asCanonicalNameString())
194+
.isEqualTo("registry.foo.com:1234/repo-here/my-name:1.0@sha256:1234abcd1234abcd1234abcd1234abcd");
195+
}
196+
197+
@Test
198+
void testDigestOnlyHasNoTag() {
199+
DockerImageName imageName = DockerImageName.parse("myname@sha256:1234abcd1234abcd1234abcd1234abcd");
200+
183201
assertThat(imageName.getVersionPart()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
202+
assertThat(imageName.getDigest()).isEqualTo("sha256:1234abcd1234abcd1234abcd1234abcd");
184203
}
185204
}
186205
}

0 commit comments

Comments
 (0)