Skip to content

Commit 355ab7b

Browse files
authored
Add unqualified flag on container ref (#397)
2 parents 1400076 + 5b2cfb0 commit 355ab7b

File tree

7 files changed

+76
-11
lines changed

7 files changed

+76
-11
lines changed

src/main/java/land/oras/ContainerRef.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,29 @@ public final class ContainerRef extends Ref<ContainerRef> {
6969
*/
7070
private final @Nullable String digest;
7171

72+
/**
73+
* Whether the container reference is unqualified without registry
74+
*/
75+
private final boolean unqualified;
76+
7277
/**
7378
* Private constructor
7479
* @param registry The registry where the container is stored.
80+
* @param unqualified Whether the container reference is unqualified without registry
7581
* @param namespace The namespace of the container.
7682
* @param repository The repository where the container is stored
7783
* @param tag The tag of the container.
7884
* @param digest The digest of the container.
7985
*/
8086
private ContainerRef(
81-
String registry, @Nullable String namespace, String repository, String tag, @Nullable String digest) {
87+
String registry,
88+
boolean unqualified,
89+
@Nullable String namespace,
90+
String repository,
91+
String tag,
92+
@Nullable String digest) {
8293
super(tag);
94+
this.unqualified = unqualified;
8395
this.registry = registry;
8496
this.namespace = namespace;
8597
this.repository = repository;
@@ -94,6 +106,28 @@ public String getRegistry() {
94106
return registry;
95107
}
96108

109+
/**
110+
* Get the effective registry based on given target
111+
* @param target The target registry
112+
* @return The effective registry
113+
*/
114+
public String getEffectiveRegistry(Registry target) {
115+
if (isUnqualified()) {
116+
if (target.getRegistry() != null) {
117+
return target.getRegistry();
118+
}
119+
}
120+
return registry;
121+
}
122+
123+
/**
124+
* Whether the container reference is unqualified without registry
125+
* @return True if unqualified
126+
*/
127+
public boolean isUnqualified() {
128+
return unqualified;
129+
}
130+
97131
/**
98132
* Get the full repository name including the namespace if any
99133
* @param registry The registry
@@ -181,7 +215,7 @@ public String getRepository() {
181215

182216
@Override
183217
public ContainerRef withDigest(String digest) {
184-
return new ContainerRef(registry, namespace, repository, tag, digest);
218+
return new ContainerRef(registry, unqualified, namespace, repository, tag, digest);
185219
}
186220

187221
@Override
@@ -329,11 +363,13 @@ public static ContainerRef parse(String name) {
329363
String repository = matcher.group(3);
330364
String tag = matcher.group(4);
331365
String digest = matcher.group(5);
366+
boolean unqualified = false;
332367
if (repository == null) {
333368
throw new IllegalArgumentException("You are minimally required to include a <namespace>/<repository>");
334369
}
335370
if (registry == null) {
336371
registry = Const.DEFAULT_REGISTRY;
372+
unqualified = true;
337373
}
338374
if (tag == null) {
339375
tag = Const.DEFAULT_TAG;
@@ -348,7 +384,7 @@ public static ContainerRef parse(String name) {
348384
SupportedAlgorithm.fromDigest(digest);
349385
}
350386

351-
return new ContainerRef(registry, namespace, repository, tag, digest);
387+
return new ContainerRef(registry, unqualified, namespace, repository, tag, digest);
352388
}
353389

354390
/**
@@ -357,7 +393,7 @@ public static ContainerRef parse(String name) {
357393
* @return The container reference
358394
*/
359395
public ContainerRef forRegistry(String registry) {
360-
return new ContainerRef(registry, namespace, repository, tag, digest);
396+
return new ContainerRef(registry, false, namespace, repository, tag, digest);
361397
}
362398

363399
/**
@@ -368,6 +404,7 @@ public ContainerRef forRegistry(String registry) {
368404
public ContainerRef forRegistry(Registry registry) {
369405
return new ContainerRef(
370406
registry.getRegistry() != null ? registry.getRegistry() : this.registry,
407+
false, // not unqualified if registry is set
371408
namespace,
372409
repository,
373410
tag,

src/main/java/land/oras/Registry.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,23 @@ public Descriptor probeDescriptor(ContainerRef ref) {
597597
return Descriptor.of(digest, 0L, contentType);
598598
}
599599

600+
/**
601+
* Return if the container ref manifests or index exists
602+
* @param containerRef The container
603+
* @return True if exists
604+
*/
605+
boolean exists(ContainerRef containerRef) {
606+
URI uri = URI.create(
607+
"%s://%s".formatted(getScheme(), containerRef.forRegistry(this).getManifestsPath(this)));
608+
HttpClient.ResponseWrapper<String> response = client.head(
609+
uri,
610+
Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE),
611+
Scopes.of(this, containerRef),
612+
authProvider);
613+
logResponse(response);
614+
return response.statusCode() == 200;
615+
}
616+
600617
/**
601618
* Get a manifest response
602619
* @param containerRef The container

src/main/java/land/oras/auth/AuthStore.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ public class AuthStore {
5050
*/
5151
private final Config config;
5252

53-
/**
54-
* Error message indicating that the format of the provided credential is invalid.
55-
* This is typically used when credentials do not match the expected structure or format.
56-
*/
57-
public static final String ERR_BAD_CREDENTIAL_FORMAT = "Bad credential format";
58-
5953
/**
6054
* Constructor for FileStore.
6155
*

src/main/java/land/oras/utils/Const.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private Const() {
3939
}
4040

4141
/**
42-
* Default registry
42+
* Default registry when no unqualified-search-registries is set in the config
4343
*/
4444
public static final String DEFAULT_REGISTRY = "docker.io";
4545

src/test/java/land/oras/ContainerRefTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ void shouldReturnWithDigest() {
3737
// Explicit
3838
ContainerRef containerRef1 = ContainerRef.parse("docker.io/library/foo");
3939
ContainerRef withDigest1 = containerRef1.withDigest("sha256@12344");
40+
assertFalse(containerRef1.isUnqualified(), "ContainerRef must be qualified");
4041
assertEquals("sha256@12344", withDigest1.getDigest());
4142
assertEquals("library", withDigest1.getNamespace());
4243
assertEquals(
@@ -159,6 +160,7 @@ void shouldParseImageWithNoDigest() {
159160
@Test
160161
void shouldParseImageWithNoRegistry() {
161162
ContainerRef containerRef = ContainerRef.parse("alpine:latest");
163+
assertTrue(containerRef.isUnqualified(), "ContainerRef must be unqualified");
162164
assertEquals("docker.io", containerRef.getRegistry());
163165
assertEquals("registry-1.docker.io", containerRef.getApiRegistry());
164166
assertEquals(
@@ -171,12 +173,20 @@ void shouldParseImageWithNoRegistry() {
171173
assertEquals("alpine", containerRef.getRepository());
172174
assertEquals("latest", containerRef.getTag());
173175
assertNull(containerRef.getDigest());
176+
assertFalse(containerRef.forRegistry("docker.io").isUnqualified(), "ContainerRef must be qualified");
174177
}
175178

176179
@Test
177180
void shouldParseImageWithNoTagAndNoRegistry() {
178181
ContainerRef containerRef = ContainerRef.parse("alpine");
179182
assertEquals("docker.io", containerRef.getRegistry());
183+
assertEquals(
184+
"docker.io",
185+
containerRef.getEffectiveRegistry(Registry.builder().build()));
186+
assertEquals(
187+
"my-registry.com",
188+
containerRef.getEffectiveRegistry(
189+
Registry.builder().withRegistry("my-registry.com").build()));
180190
assertEquals("library", containerRef.getNamespace());
181191
assertEquals(
182192
"library/alpine",

src/test/java/land/oras/RegistryTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,8 @@ void testShouldPushAndPullMinimalArtifact() throws IOException {
693693
.build();
694694
ContainerRef containerRef =
695695
ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));
696+
ContainerRef unknown =
697+
ContainerRef.parse("%s/library/artifact-full:unknown".formatted(this.registry.getRegistry()));
696698

697699
Path file1 = blobDir.resolve("file1.txt");
698700
Files.writeString(file1, "foobar");
@@ -703,6 +705,9 @@ void testShouldPushAndPullMinimalArtifact() throws IOException {
703705
assertEquals(
704706
Const.DEFAULT_ARTIFACT_MEDIA_TYPE, manifest.getArtifactType().getMediaType());
705707

708+
assertTrue(registry.exists(containerRef), "Artifact should exist");
709+
assertFalse(registry.exists(unknown), "Artifact should not exist");
710+
706711
// Ensure one annotation (created by the SDK)
707712
Map<String, String> manifestAnnotations = manifest.getAnnotations();
708713
assertEquals(1, manifestAnnotations.size(), "Annotations size is incorrect");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
junit.jupiter.execution.parallel.enabled=true
22
junit.jupiter.execution.parallel.mode.classes.default=concurrent
3+
junit.jupiter.execution.parallel.config.strategy=fixed
4+
junit.jupiter.execution.parallel.config.fixed.parallelism=5

0 commit comments

Comments
 (0)