3333import java .util .Map ;
3434import java .util .Objects ;
3535import java .util .Optional ;
36+ import java .util .stream .Collectors ;
3637import land .oras .ContainerRef ;
3738import land .oras .OrasModel ;
3839import land .oras .Registry ;
@@ -104,17 +105,28 @@ public static RegistriesConf newConf() {
104105 * The model of a mirror entry within a [[registry]] table.
105106 * @param location The mirror registry location (host[:port][/path]).
106107 * @param insecure Whether the mirror is insecure.
108+ * @param pullFromMirror Controls which pull operations may use this mirror (default: {@link PullFromMirror#ALL}).
107109 */
108110 @ OrasModel
109111 public record MirrorConfig (
110- @ Nullable @ JsonProperty ("location" ) String location , @ Nullable @ JsonProperty ("insecure" ) Boolean insecure ) {
112+ @ Nullable @ JsonProperty ("location" ) String location ,
113+ @ Nullable @ JsonProperty ("insecure" ) Boolean insecure ,
114+ @ Nullable @ JsonProperty ("pull-from-mirror" ) PullFromMirror pullFromMirror ) {
111115 /**
112116 * Return true if this mirror should be accessed over plain HTTP or with unverified TLS.
113117 * @return true if insecure
114118 */
115119 public boolean isInsecure () {
116120 return insecure != null && insecure ;
117121 }
122+
123+ /**
124+ * Return the effective pull-from-mirror setting, defaulting to {@link PullFromMirror#ALL}.
125+ * @return the pull-from-mirror setting
126+ */
127+ public PullFromMirror effectivePullFromMirror () {
128+ return pullFromMirror != null ? pullFromMirror : PullFromMirror .ALL ;
129+ }
118130 }
119131
120132 /**
@@ -123,6 +135,7 @@ public boolean isInsecure() {
123135 * @param location The registry location
124136 * @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images.
125137 * @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.
138+ * @param mirrorByDigestOnly If true, all mirrors for this registry are treated as digest-only (equivalent to setting pull-from-mirror=digest-only on every mirror).
126139 * @param mirrors Ordered list of mirror entries to try before the registry location.
127140 */
128141 @ OrasModel
@@ -131,6 +144,7 @@ record RegistryConfig(
131144 @ Nullable @ JsonProperty ("location" ) String location ,
132145 @ Nullable @ JsonProperty ("blocked" ) Boolean blocked ,
133146 @ Nullable @ JsonProperty ("insecure" ) Boolean insecure ,
147+ @ Nullable @ JsonProperty ("mirror-by-digest-only" ) Boolean mirrorByDigestOnly ,
134148 @ Nullable @ JsonProperty ("mirror" ) List <MirrorConfig > mirrors ) {
135149 /**
136150 * Return true if this registry is blocked and cannot be used for pulling or pushing images.
@@ -147,6 +161,14 @@ public boolean isBlocked() {
147161 public boolean isInsecure () {
148162 return insecure != null && insecure ;
149163 }
164+
165+ /**
166+ * Return true if all mirrors for this registry should only be used when the reference includes a digest.
167+ * @return true if mirror-by-digest-only is set
168+ */
169+ public boolean isMirrorByDigestOnly () {
170+ return mirrorByDigestOnly != null && mirrorByDigestOnly ;
171+ }
150172 }
151173
152174 /**
@@ -205,6 +227,40 @@ public String getKey() {
205227 private String value ;
206228 }
207229
230+ /**
231+ * Controls which pull operations may use a mirror.
232+ * <ul>
233+ * <li>{@link #ALL} – the mirror is used for all pull operations (default)</li>
234+ * <li>{@link #DIGEST_ONLY} – mirror is only used when the reference includes a digest</li>
235+ * <li>{@link #TAG_ONLY} – mirror is only used when the reference includes a tag</li>
236+ * </ul>
237+ */
238+ @ OrasModel
239+ public enum PullFromMirror {
240+ ALL ("all" ),
241+ DIGEST_ONLY ("digest-only" ),
242+ TAG_ONLY ("tag-only" );
243+
244+ PullFromMirror (String value ) {
245+ this .value = value ;
246+ }
247+
248+ @ JsonCreator
249+ public static PullFromMirror fromString (String key ) {
250+ for (PullFromMirror v : values ()) {
251+ if (v .value .equalsIgnoreCase (key )) return v ;
252+ }
253+ throw new IllegalArgumentException ("Unknown pull-from-mirror value: " + key );
254+ }
255+
256+ @ JsonValue
257+ public String getKey () {
258+ return value ;
259+ }
260+
261+ private final String value ;
262+ }
263+
208264 /**
209265 * The model of the configuration file, which contains the list of registry configurations, aliases, and unqualified registries.
210266 * @param registries The list of registry configurations, each containing the registry location, whether it is blocked, and whether it is insecure.
@@ -377,6 +433,35 @@ public List<MirrorConfig> getMirrors(ContainerRef ref) {
377433 return Collections .unmodifiableList (matchingConfig .get ().mirrors ());
378434 }
379435
436+ /**
437+ * Return the ordered list of mirrors that are applicable for the given reference, filtering out mirrors
438+ * whose {@code pull-from-mirror} setting does not match the reference type (tag vs. digest).
439+ * The registry-level {@code mirror-by-digest-only} flag, when true, overrides all per-mirror settings
440+ * and restricts every mirror to digest-only pulls.
441+ * @param ref the container reference to look up applicable mirrors for.
442+ * @return an unmodifiable list of applicable mirror configs (may be empty).
443+ */
444+ public List <MirrorConfig > getApplicableMirrors (ContainerRef ref ) {
445+ Optional <RegistryConfig > matchingConfig = selectMatchingTable (ref );
446+ if (matchingConfig .isEmpty () || matchingConfig .get ().mirrors () == null ) {
447+ return Collections .emptyList ();
448+ }
449+ boolean registryDigestOnly = matchingConfig .get ().isMirrorByDigestOnly ();
450+ boolean refHasDigest = ref .getDigest () != null && !ref .getDigest ().isEmpty ();
451+ boolean refHasTag = ref .getTag () != null && !ref .getTag ().isEmpty ();
452+ return matchingConfig .get ().mirrors ().stream ()
453+ .filter (mirror -> {
454+ PullFromMirror effective =
455+ registryDigestOnly ? PullFromMirror .DIGEST_ONLY : mirror .effectivePullFromMirror ();
456+ return switch (effective ) {
457+ case DIGEST_ONLY -> refHasDigest ;
458+ case TAG_ONLY -> refHasTag ;
459+ case ALL -> true ;
460+ };
461+ })
462+ .collect (Collectors .toUnmodifiableList ());
463+ }
464+
380465 /**
381466 * Rewrite the given container reference to use the mirror's location, replacing the registry host.
382467 * @param ref the original container reference.
0 commit comments