|
35 | 35 | import java.util.Optional; |
36 | 36 | import land.oras.ContainerRef; |
37 | 37 | import land.oras.OrasModel; |
| 38 | +import land.oras.Registry; |
38 | 39 | import land.oras.exception.OrasException; |
39 | 40 | import land.oras.utils.Const; |
40 | 41 | import land.oras.utils.TomlUtils; |
@@ -99,23 +100,50 @@ public static RegistriesConf newConf() { |
99 | 100 | return newConf(paths); |
100 | 101 | } |
101 | 102 |
|
| 103 | + /** |
| 104 | + * The model of a mirror entry within a [[registry]] table. |
| 105 | + * @param location The mirror registry location (host[:port][/path]). |
| 106 | + * @param insecure Whether the mirror is insecure. |
| 107 | + */ |
| 108 | + @OrasModel |
| 109 | + public record MirrorConfig( |
| 110 | + @Nullable @JsonProperty("location") String location, @Nullable @JsonProperty("insecure") Boolean insecure) { |
| 111 | + /** |
| 112 | + * Return true if this mirror should be accessed over plain HTTP or with unverified TLS. |
| 113 | + * @return true if insecure |
| 114 | + */ |
| 115 | + public boolean isInsecure() { |
| 116 | + return insecure != null && insecure; |
| 117 | + } |
| 118 | + } |
| 119 | + |
102 | 120 | /** |
103 | 121 | * The model of the registry configuration |
104 | 122 | * @param prefix The prefix to match against container references. |
105 | 123 | * @param location The registry location |
106 | 124 | * @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images. |
107 | 125 | * @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. |
| 126 | + * @param mirrors Ordered list of mirror entries to try before the registry location. |
108 | 127 | */ |
109 | 128 | @OrasModel |
110 | 129 | record RegistryConfig( |
111 | 130 | @Nullable @JsonProperty("prefix") String prefix, |
112 | 131 | @Nullable @JsonProperty("location") String location, |
113 | 132 | @Nullable @JsonProperty("blocked") Boolean blocked, |
114 | | - @Nullable @JsonProperty("insecure") Boolean insecure) { |
| 133 | + @Nullable @JsonProperty("insecure") Boolean insecure, |
| 134 | + @Nullable @JsonProperty("mirror") List<MirrorConfig> mirrors) { |
| 135 | + /** |
| 136 | + * Return true if this registry is blocked and cannot be used for pulling or pushing images. |
| 137 | + * @return true if blocked |
| 138 | + */ |
115 | 139 | public boolean isBlocked() { |
116 | 140 | return blocked != null && blocked; |
117 | 141 | } |
118 | 142 |
|
| 143 | + /** |
| 144 | + * Return true if this registry should be accessed over plain HTTP or with unverified TLS. |
| 145 | + * @return true if insecure |
| 146 | + */ |
119 | 147 | public boolean isInsecure() { |
120 | 148 | return insecure != null && insecure; |
121 | 149 | } |
@@ -254,11 +282,14 @@ public boolean isBlocked(ContainerRef location) { |
254 | 282 |
|
255 | 283 | /** |
256 | 284 | * Check if the given registry is marked as insecure in the configuration. |
| 285 | + * If no entry found, we fall back to Registry configuration |
| 286 | + * If the entry is found we use the insecure flag (or default true) |
| 287 | + * @param registry The registry object |
257 | 288 | * @param location the registry location to check for insecurity. |
258 | 289 | * @return true if the registry is marked as insecure, false otherwise. |
259 | 290 | */ |
260 | | - public boolean isInsecure(ContainerRef location) { |
261 | | - return selectMatchingTable(location).map(RegistryConfig::isInsecure).orElse(false); |
| 291 | + public boolean isInsecure(Registry registry, ContainerRef location) { |
| 292 | + return selectMatchingTable(location).map(RegistryConfig::isInsecure).orElse(registry.isInsecure()); |
262 | 293 | } |
263 | 294 |
|
264 | 295 | /** |
@@ -333,6 +364,58 @@ public ContainerRef rewrite(ContainerRef ref) { |
333 | 364 | return ContainerRef.parse(rewrittenRefString); |
334 | 365 | } |
335 | 366 |
|
| 367 | + /** |
| 368 | + * Return the ordered list of mirrors configured for the registry that matches the given reference. |
| 369 | + * @param ref the container reference to look up mirrors for. |
| 370 | + * @return an unmodifiable list of mirror configs (may be empty). |
| 371 | + */ |
| 372 | + public List<MirrorConfig> getMirrors(ContainerRef ref) { |
| 373 | + Optional<RegistryConfig> matchingConfig = selectMatchingTable(ref); |
| 374 | + if (matchingConfig.isEmpty() || matchingConfig.get().mirrors() == null) { |
| 375 | + return Collections.emptyList(); |
| 376 | + } |
| 377 | + return Collections.unmodifiableList(matchingConfig.get().mirrors()); |
| 378 | + } |
| 379 | + |
| 380 | + /** |
| 381 | + * Rewrite the given container reference to use the mirror's location, replacing the registry host. |
| 382 | + * @param ref the original container reference. |
| 383 | + * @param mirror the mirror configuration to apply. |
| 384 | + * @return the rewritten reference pointing at the mirror. |
| 385 | + */ |
| 386 | + public ContainerRef rewriteForMirror(ContainerRef ref, MirrorConfig mirror) { |
| 387 | + String mirrorLocation = mirror.location(); |
| 388 | + if (mirrorLocation == null || mirrorLocation.isBlank()) { |
| 389 | + return ref; |
| 390 | + } |
| 391 | + // Strip trailing slashes to prevent double-slash segments in the rewritten ref |
| 392 | + mirrorLocation = mirrorLocation.replaceAll("/+$", ""); |
| 393 | + if (mirrorLocation.isBlank()) { |
| 394 | + return ref; |
| 395 | + } |
| 396 | + // Always build from components to correctly handle: |
| 397 | + // - unqualified refs (toString() omits the registry) |
| 398 | + // - digest-only refs (tag is null, toString() would produce ":null") |
| 399 | + String namespace = ref.getNamespace(); |
| 400 | + String repository = ref.getRepository(); |
| 401 | + String tag = ref.getTag(); |
| 402 | + String digest = ref.getDigest(); |
| 403 | + StringBuilder sb = new StringBuilder(mirrorLocation); |
| 404 | + if (namespace != null && !namespace.isEmpty()) { |
| 405 | + sb.append("/").append(namespace); |
| 406 | + } |
| 407 | + sb.append("/").append(repository); |
| 408 | + if (tag != null && !tag.isEmpty()) { |
| 409 | + sb.append(":").append(tag); |
| 410 | + } |
| 411 | + if (digest != null && !digest.isEmpty()) { |
| 412 | + sb.append("@").append(digest); |
| 413 | + } |
| 414 | + String rewrittenRefString = sb.toString(); |
| 415 | + LOG.debug("Rewriting '{}' to mirror '{}'", ref, rewrittenRefString); |
| 416 | + return ContainerRef.parse(rewrittenRefString); |
| 417 | + } |
| 418 | + |
336 | 419 | /** |
337 | 420 | * Select the matching registry configuration table for the container reference. |
338 | 421 | * @param ref the container reference to find the matching registry configuration for. |
|
0 commit comments