@@ -99,23 +99,50 @@ public static RegistriesConf newConf() {
9999 return newConf (paths );
100100 }
101101
102+ /**
103+ * The model of a mirror entry within a [[registry]] table.
104+ * @param location The mirror registry location (host[:port][/path]).
105+ * @param insecure Whether the mirror is insecure.
106+ */
107+ @ OrasModel
108+ public record MirrorConfig (
109+ @ Nullable @ JsonProperty ("location" ) String location , @ Nullable @ JsonProperty ("insecure" ) Boolean insecure ) {
110+ /**
111+ * Return true if this mirror should be accessed over plain HTTP or with unverified TLS.
112+ * @return true if insecure
113+ */
114+ public boolean isInsecure () {
115+ return insecure != null && insecure ;
116+ }
117+ }
118+
102119 /**
103120 * The model of the registry configuration
104121 * @param prefix The prefix to match against container references.
105122 * @param location The registry location
106123 * @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images.
107124 * @param insecure Whether the registry is insecure. If true, the registry is considered insecure and may allow connections over HTTP or with invalid TLS certificates.
125+ * @param mirrors Ordered list of mirror entries to try before the registry location.
108126 */
109127 @ OrasModel
110128 record RegistryConfig (
111129 @ Nullable @ JsonProperty ("prefix" ) String prefix ,
112130 @ Nullable @ JsonProperty ("location" ) String location ,
113131 @ Nullable @ JsonProperty ("blocked" ) Boolean blocked ,
114- @ Nullable @ JsonProperty ("insecure" ) Boolean insecure ) {
132+ @ Nullable @ JsonProperty ("insecure" ) Boolean insecure ,
133+ @ Nullable @ JsonProperty ("mirror" ) List <MirrorConfig > mirrors ) {
134+ /**
135+ * Return true if this registry is blocked and cannot be used for pulling or pushing images.
136+ * @return true if blocked
137+ */
115138 public boolean isBlocked () {
116139 return blocked != null && blocked ;
117140 }
118141
142+ /**
143+ * Return true if this registry should be accessed over plain HTTP or with unverified TLS.
144+ * @return true if insecure
145+ */
119146 public boolean isInsecure () {
120147 return insecure != null && insecure ;
121148 }
@@ -333,6 +360,69 @@ public ContainerRef rewrite(ContainerRef ref) {
333360 return ContainerRef .parse (rewrittenRefString );
334361 }
335362
363+ /**
364+ * Return the explicitly configured insecure value for the registry matching the given reference.
365+ * Returns empty when no matching entry exists or the field is not set (null), allowing callers
366+ * to distinguish "config says false" from "config has no opinion".
367+ * @param ref the container reference to look up.
368+ * @return Optional of true/false when explicitly configured, empty otherwise.
369+ */
370+ public Optional <Boolean > getInsecure (ContainerRef ref ) {
371+ return selectMatchingTable (ref ).map (RegistryConfig ::insecure );
372+ }
373+
374+ /**
375+ * Return the ordered list of mirrors configured for the registry that matches the given reference.
376+ * @param ref the container reference to look up mirrors for.
377+ * @return an unmodifiable list of mirror configs (may be empty).
378+ */
379+ public List <MirrorConfig > getMirrors (ContainerRef ref ) {
380+ Optional <RegistryConfig > matchingConfig = selectMatchingTable (ref );
381+ if (matchingConfig .isEmpty () || matchingConfig .get ().mirrors () == null ) {
382+ return Collections .emptyList ();
383+ }
384+ return Collections .unmodifiableList (matchingConfig .get ().mirrors ());
385+ }
386+
387+ /**
388+ * Rewrite the given container reference to use the mirror's location, replacing the registry host.
389+ * @param ref the original container reference.
390+ * @param mirror the mirror configuration to apply.
391+ * @return the rewritten reference pointing at the mirror.
392+ */
393+ public ContainerRef rewriteForMirror (ContainerRef ref , MirrorConfig mirror ) {
394+ String mirrorLocation = mirror .location ();
395+ if (mirrorLocation == null || mirrorLocation .isBlank ()) {
396+ return ref ;
397+ }
398+ // Strip trailing slashes to prevent double-slash segments in the rewritten ref
399+ mirrorLocation = mirrorLocation .replaceAll ("/+$" , "" );
400+ if (mirrorLocation .isBlank ()) {
401+ return ref ;
402+ }
403+ // Always build from components to correctly handle:
404+ // - unqualified refs (toString() omits the registry)
405+ // - digest-only refs (tag is null, toString() would produce ":null")
406+ String namespace = ref .getNamespace ();
407+ String repository = ref .getRepository ();
408+ String tag = ref .getTag ();
409+ String digest = ref .getDigest ();
410+ StringBuilder sb = new StringBuilder (mirrorLocation );
411+ if (namespace != null && !namespace .isEmpty ()) {
412+ sb .append ("/" ).append (namespace );
413+ }
414+ sb .append ("/" ).append (repository );
415+ if (tag != null && !tag .isEmpty ()) {
416+ sb .append (":" ).append (tag );
417+ }
418+ if (digest != null && !digest .isEmpty ()) {
419+ sb .append ("@" ).append (digest );
420+ }
421+ String rewrittenRefString = sb .toString ();
422+ LOG .debug ("Rewriting '{}' to mirror '{}'" , ref , rewrittenRefString );
423+ return ContainerRef .parse (rewrittenRefString );
424+ }
425+
336426 /**
337427 * Select the matching registry configuration table for the container reference.
338428 * @param ref the container reference to find the matching registry configuration for.
0 commit comments