Container Registry¶
This example models an OCI/Docker container image registry stored in S3.
It demonstrates deep interface chaining, @Name for underscore-prefixed keys,
getValueAsStream() for binary data, and default methods with domain logic.
S3 Bucket Structure¶
v2/
repositories/
myorg/
api-server/
_manifests/
tags/
latest/
current/
link
v1.0.0/
current/
link
v1.1.0/
current/
link
revisions/
sha256/
abc123.../
link
_layers/
sha256/
def456.../
link
789abc.../
link
web-frontend/
_manifests/
tags/
latest/
current/
link
_layers/
sha256/
aaa111.../
link
blobs/
sha256/
abc123.../
data
def456.../
data
789abc.../
data
Interfaces¶
// Registry root — the v2/ prefix
public interface Registry extends S3.Dir {
Repositories repositories();
BlobStore blobs();
}
// The repositories/ directory
public interface Repositories extends S3.Dir {
// Navigate to a namespace (e.g., "myorg")
Namespace namespace(String name);
Stream<Namespace> namespaces();
}
// A namespace (organization or user)
public interface Namespace extends S3.Dir {
ImageRepo image(String name);
Stream<ImageRepo> images();
}
// A single image repository (e.g., api-server)
public interface ImageRepo extends S3.Dir {
// @Name handles the underscore-prefixed directory names
@Name("_manifests")
Manifests manifests();
@Name("_layers")
Layers layers();
}
// The _manifests/ directory
public interface Manifests extends S3.Dir {
Tags tags();
Revisions revisions();
}
// The tags/ directory — lists all available tags
public interface Tags extends S3.Dir {
Tag tag(String name);
Stream<Tag> tags();
}
// A single tag (e.g., "latest", "v1.0.0")
public interface Tag extends S3.Dir {
Current current();
}
// The current/ subdirectory under a tag
public interface Current extends S3.Dir {
LinkFile link();
}
// The revisions/ directory
public interface Revisions extends S3.Dir {
DigestDir sha256();
}
// The _layers/ directory
public interface Layers extends S3.Dir {
DigestDir sha256();
}
// A sha256/ directory containing digest-named subdirectories
public interface DigestDir extends S3.Dir {
DigestEntry digest(String hash);
Stream<DigestEntry> digests();
}
// A single digest entry directory
public interface DigestEntry extends S3.Dir {
LinkFile link();
}
// A link file — contains a digest reference as text
public interface LinkFile extends S3.File {
// Parse the digest algorithm from the link content
default String getAlgorithm() {
String content = getValueAsString().trim();
return content.substring(0, content.indexOf(':'));
}
// Parse the digest hash from the link content
default String getDigest() {
String content = getValueAsString().trim();
return content.substring(content.indexOf(':') + 1);
}
}
// The blobs/ directory at the registry root
public interface BlobStore extends S3.Dir {
DigestDir sha256();
}
// A blob's data file — binary content
public interface BlobData extends S3.File {
default String getContentType() {
return getObjectMetadata().contentType();
}
default boolean isManifest() {
String ct = getContentType();
return ct != null && ct.contains("manifest");
}
}
Usage¶
Resolve an image tag to its digest¶
Navigate through the deep interface chain to read the digest a tag points to:
Registry registry = bucket.as(Registry.class);
ImageRepo apiServer = registry.repositories()
.namespace("myorg")
.image("api-server");
// Follow the chain: _manifests → tags → v1.0.0 → current → link
LinkFile link = apiServer.manifests()
.tags()
.tag("v1.0.0")
.current()
.link();
System.out.println(link.getAlgorithm());
// Output: sha256
System.out.println(link.getDigest());
// Output: abc123...
List all tags for an image¶
apiServer.manifests().tags().tags().forEach(tag -> {
System.out.println(tag.file().getName());
});
// Output:
// latest
// v1.0.0
// v1.1.0
Access binary blob data¶
getValueAsStream() returns the raw bytes for binary objects like
container layers:
BlobStore blobs = registry.blobs();
DigestEntry entry = blobs.sha256().digest("def456...");
LinkFile blobLink = entry.link();
System.out.println(blobLink.getValueAsString().trim());
// Output: sha256:def456...
// Read binary blob content as a stream
BlobData blob = blobs.sha256().digest("abc123...").data();
try (InputStream is = blob.getValueAsStream()) {
// Process the binary content...
}
Inspect content types¶
Default methods on BlobData add domain logic for identifying blob types:
BlobData blob = registry.blobs().sha256().digest("abc123...").data();
System.out.println(blob.getContentType());
// Output: application/vnd.oci.image.manifest.v1+json
System.out.println(blob.isManifest());
// Output: true
Features Used¶
@Name— maps methods to_manifests,_layers, and other keys that start with underscores- Deep interface chaining — models the nested registry path:
repositories → namespace → image → _manifests → tags → tag → current → link getValueAsStream()— reads binary blob data- Default methods —
LinkFile.getAlgorithm(),LinkFile.getDigest(),BlobData.isManifest()add parsing logic getObjectMetadata()—BlobData.getContentType()reads the S3 content type
Test¶
See ContainerRegistryTest.java for a working test of this example.