2222
2323import java .net .URLEncoder ;
2424import java .nio .charset .StandardCharsets ;
25+ import java .nio .file .Path ;
2526import java .util .Objects ;
2627import java .util .regex .Matcher ;
2728import java .util .regex .Pattern ;
3031import land .oras .utils .SupportedAlgorithm ;
3132import org .jspecify .annotations .NullMarked ;
3233import org .jspecify .annotations .Nullable ;
34+ import org .slf4j .Logger ;
35+ import org .slf4j .LoggerFactory ;
3336
3437/**
3538 * A referer of a container on a {@link Registry}.
3841@ OrasModel
3942public final class ContainerRef extends Ref <ContainerRef > {
4043
44+ /**
45+ * The logger for this class.
46+ */
47+ public static final Logger LOG = LoggerFactory .getLogger (ContainerRef .class );
48+
4149 /**
4250 * The regex pattern to parse the container name including the registry, namespace, repository, tag and digest.
4351 */
@@ -106,20 +114,6 @@ public String getRegistry() {
106114 return registry ;
107115 }
108116
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-
123117 /**
124118 * Whether the container reference is unqualified without registry
125119 * @return True if unqualified
@@ -387,6 +381,23 @@ public static ContainerRef parse(String name) {
387381 return new ContainerRef (registry , unqualified , namespace , repository , tag , digest );
388382 }
389383
384+ /**
385+ * Get the effective registry based on given target
386+ * This methods will perform HEAD request to determine the first unqualified search registry that contains the container reference if the reference is unqualified, otherwise return the registry of the reference.
387+ * This only works with Manifests and Index but now direct blob access.
388+ * See {@link #forRegistry(String)} so set correct registry when getting blobs outside high level API like {@link Registry#pullArtifact(ContainerRef, Path, boolean)}.
389+ * @param target The target registry
390+ * @return The effective registry
391+ */
392+ public String getEffectiveRegistry (Registry target ) {
393+ if (isUnqualified ()) {
394+ return target .getRegistry () != null
395+ ? target .getRegistry ()
396+ : determineFirstUnqualifiedSearchRegistry (target );
397+ }
398+ return registry ;
399+ }
400+
390401 /**
391402 * Return a copy of reference for a registry other registry
392403 * @param registry The registry
@@ -402,6 +413,13 @@ public ContainerRef forRegistry(String registry) {
402413 * @return The container reference
403414 */
404415 public ContainerRef forRegistry (Registry registry ) {
416+ if (isUnqualified () && registry .getRegistry () == null ) {
417+ LOG .info (
418+ "The container reference {} was created without a registry. Will try to resolve using unqualified-search-registries in order" ,
419+ this );
420+ return new ContainerRef (
421+ determineFirstUnqualifiedSearchRegistry (registry ), false , namespace , repository , tag , digest );
422+ }
405423 return new ContainerRef (
406424 registry .getRegistry () != null ? registry .getRegistry () : this .registry ,
407425 false , // not unqualified if registry is set
@@ -411,6 +429,27 @@ public ContainerRef forRegistry(Registry registry) {
411429 digest );
412430 }
413431
432+ private String determineFirstUnqualifiedSearchRegistry (Registry registry ) {
433+ // No settings, keep old behavior of defaulting to docker.io for unqualified reference
434+ if (registry .getRegistriesConf ().getUnqualifiedRegistries ().isEmpty ()) {
435+ return Const .DEFAULT_REGISTRY ;
436+ }
437+ LOG .debug (
438+ "Found registries in unqualified-search-registries: {}" ,
439+ registry .getRegistriesConf ().getUnqualifiedRegistries ());
440+ for (String searchRegistry : registry .getRegistriesConf ().getUnqualifiedRegistries ()) {
441+ Registry targetRegistry = registry .copy (registry , searchRegistry );
442+ LOG .debug ("Checking if container {} exists in unqualified search registry {}" , this , searchRegistry );
443+ if (targetRegistry .exists (this )) {
444+ LOG .debug ("Found container {} in unqualified search registry {}" , this , searchRegistry );
445+ return searchRegistry ;
446+ }
447+ }
448+ throw new OrasException (
449+ "Container reference %s is unqualified and cannot be found in any of the unqualified search registries: %s"
450+ .formatted (this , registry .getRegistriesConf ().getUnqualifiedRegistries ()));
451+ }
452+
414453 @ Override
415454 public boolean equals (Object o ) {
416455 if (o == null || getClass () != o .getClass ()) return false ;
@@ -429,6 +468,12 @@ public int hashCode() {
429468
430469 @ Override
431470 public String toString () {
471+ if (isUnqualified ()) {
472+ if (namespace != null && !namespace .isEmpty ()) {
473+ return "%s/%s:%s%s" .formatted (namespace , repository , tag , digest != null ? "@" + digest : "" );
474+ }
475+ return "%s:%s%s" .formatted (repository , tag , digest != null ? "@" + digest : "" );
476+ }
432477 if (namespace != null && !namespace .isEmpty ()) {
433478 return "%s/%s/%s:%s%s" .formatted (registry , namespace , repository , tag , digest != null ? "@" + digest : "" );
434479 }
0 commit comments