Skip to content

Commit cd41068

Browse files
committed
Add support for registries mirror
Signed-off-by: Valentin Delaye <jonesbusy@users.noreply.github.com>
1 parent df87bc6 commit cd41068

7 files changed

Lines changed: 738 additions & 31 deletions

File tree

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.nio.file.Path;
2626
import java.util.List;
2727
import java.util.Objects;
28+
import java.util.Optional;
2829
import java.util.regex.Matcher;
2930
import java.util.regex.Pattern;
3031
import land.oras.exception.OrasException;
@@ -506,12 +507,17 @@ public ContainerRef forRegistry(String registry) {
506507
public boolean isInsecure(Registry registry) {
507508
String effectiveRegistry = getEffectiveRegistry(registry);
508509
ContainerRef effectiveRef = forRegistry(effectiveRegistry);
509-
if (registry.getRegistriesConf().isInsecure(effectiveRef)) {
510-
LOG.debug(
511-
"Access to container reference {} is insecure by location configuration for registry {}",
512-
this,
513-
effectiveRegistry);
514-
return true;
510+
// Config is authoritative when explicitly set (true or false).
511+
// This allows a config entry with insecure=false to downgrade an otherwise-insecure registry.
512+
Optional<Boolean> configured = registry.getRegistriesConf().getInsecure(effectiveRef);
513+
if (configured.isPresent()) {
514+
if (Boolean.TRUE.equals(configured.get())) {
515+
LOG.debug(
516+
"Access to container reference {} is insecure by location configuration for registry {}",
517+
this,
518+
effectiveRegistry);
519+
}
520+
return Boolean.TRUE.equals(configured.get());
515521
}
516522
return registry.isInsecure();
517523
}

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

Lines changed: 160 additions & 6 deletions
Large diffs are not rendered by default.

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

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.

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)