Skip to content

Commit e0611a8

Browse files
authored
Add support for mirrors on registries.conf (#745)
Signed-off-by: Valentin Delaye <jonesbusy@users.noreply.github.com>
1 parent df87bc6 commit e0611a8

8 files changed

Lines changed: 928 additions & 71 deletions

File tree

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ public String getEffectiveRegistry(Registry target) {
484484
? target.getRegistry()
485485
: determineFirstUnqualifiedSearchRegistry(target);
486486
}
487-
// The effective registry can we rewrotten by the registry configuration.
487+
// The effective registry can be rewritten by the registry configuration.
488488
// Ensure to return it
489489
ContainerRef rewrite = target.getRegistriesConf().rewrite(this);
490490
return rewrite.getRegistry();
@@ -506,14 +506,15 @@ public ContainerRef forRegistry(String registry) {
506506
public boolean isInsecure(Registry registry) {
507507
String effectiveRegistry = getEffectiveRegistry(registry);
508508
ContainerRef effectiveRef = forRegistry(effectiveRegistry);
509-
if (registry.getRegistriesConf().isInsecure(effectiveRef)) {
509+
// Configuration is authoritative over the current registry
510+
if (registry.getRegistriesConf().isInsecure(registry, effectiveRef)) {
510511
LOG.debug(
511512
"Access to container reference {} is insecure by location configuration for registry {}",
512513
this,
513514
effectiveRegistry);
514515
return true;
515516
}
516-
return registry.isInsecure();
517+
return false;
517518
}
518519

519520
/**

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

Lines changed: 165 additions & 7 deletions
Large diffs are not rendered by default.

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

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.Optional;
3636
import land.oras.ContainerRef;
3737
import land.oras.OrasModel;
38+
import land.oras.Registry;
3839
import land.oras.exception.OrasException;
3940
import land.oras.utils.Const;
4041
import land.oras.utils.TomlUtils;
@@ -99,23 +100,50 @@ public static RegistriesConf newConf() {
99100
return newConf(paths);
100101
}
101102

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+
102120
/**
103121
* The model of the registry configuration
104122
* @param prefix The prefix to match against container references.
105123
* @param location The registry location
106124
* @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images.
107125
* @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.
108127
*/
109128
@OrasModel
110129
record RegistryConfig(
111130
@Nullable @JsonProperty("prefix") String prefix,
112131
@Nullable @JsonProperty("location") String location,
113132
@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+
*/
115139
public boolean isBlocked() {
116140
return blocked != null && blocked;
117141
}
118142

143+
/**
144+
* Return true if this registry should be accessed over plain HTTP or with unverified TLS.
145+
* @return true if insecure
146+
*/
119147
public boolean isInsecure() {
120148
return insecure != null && insecure;
121149
}
@@ -254,11 +282,14 @@ public boolean isBlocked(ContainerRef location) {
254282

255283
/**
256284
* 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
257288
* @param location the registry location to check for insecurity.
258289
* @return true if the registry is marked as insecure, false otherwise.
259290
*/
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());
262293
}
263294

264295
/**
@@ -333,6 +364,58 @@ public ContainerRef rewrite(ContainerRef ref) {
333364
return ContainerRef.parse(rewrittenRefString);
334365
}
335366

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+
336419
/**
337420
* Select the matching registry configuration table for the container reference.
338421
* @param ref the container reference to find the matching registry configuration for.

src/test/java/land/oras/ClassAnnotationsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ void shouldHaveAnnotationOnModel() {
4949
.loadClasses());
5050

5151
// Check number of classes
52-
assertEquals(26, modelClasses.size());
52+
assertEquals(27, modelClasses.size());
5353

5454
// Check classes
5555
assertTrue(modelClasses.contains(Annotations.class));
@@ -83,7 +83,7 @@ void shouldHaveAnnotationOnAuthPackage() {
8383
.loadClasses());
8484

8585
// Check number of classes
86-
assertEquals(8, modelClasses.size());
86+
assertEquals(9, modelClasses.size());
8787
}
8888
}
8989
}

0 commit comments

Comments
 (0)