Optionally resolve wrapper index to single-platform manifest based on com.apple.containerization.index.indirect annotation (#397)

Fixes #212

This PR:
- adds a `package func resolved()` on `ClientImage` that makes use of
the new `com.apple.containerization.index.indirect` annotation to
identify and resolve wrapper indices created by Containerization;
- replace the digest displayed in `container image list` with the one of
the resolved manifest;
- use the resolved manifest for `container image inspect` if the index
is a wrapper.
This commit is contained in:
YR Chen
2025-08-05 02:14:00 +08:00
committed by GitHub
parent 9e9d056339
commit ce431b5c8f
3 changed files with 24 additions and 4 deletions
+4 -2
View File
@@ -51,6 +51,7 @@ extension Application {
var rows = createVerboseHeader()
for image in images {
let formatter = ByteCountFormatter()
let imageDigest = try await image.resolved().digest
for descriptor in try await image.index().manifests {
// Don't list attestation manifests
if let referenceType = descriptor.annotations?["vnd.docker.reference.type"],
@@ -85,7 +86,7 @@ extension Application {
let row = [
reference.name,
reference.tag ?? "<none>",
Utility.trimDigest(digest: image.descriptor.digest),
Utility.trimDigest(digest: imageDigest),
os,
arch,
variant,
@@ -130,10 +131,11 @@ extension Application {
for image in images {
let processedReferenceString = try ClientImage.denormalizeReference(image.reference)
let reference = try ContainerizationOCI.Reference.parse(processedReferenceString)
let digest = try await image.resolved().digest
rows.append([
reference.name,
reference.tag ?? "<none>",
Utility.trimDigest(digest: image.descriptor.digest),
Utility.trimDigest(digest: digest),
])
}
let formatter = TableOutput(rows: rows)
@@ -69,6 +69,24 @@ public struct ClientImage: Sendable {
}
return try content.decode()
}
/// Returns the resolved OCI descriptor for the image.
package func resolved() async throws -> Descriptor {
let index = try await self.index()
let indirect = index.annotations?[AnnotationKeys.containerizationIndexIndirect]
// If this is not an indirect index, return its own descriptor
guard let indirect, ["1", "true"].contains(indirect.lowercased()) else {
return self.descriptor
}
// For indirect indices, return the first (and only) manifest
guard let manifest = index.manifests.first else {
throw ContainerizationError(
.internalError,
message: "Failed to resolve indirect index \(self.digest): no manifests found"
)
}
return manifest
}
}
// MARK: ClientImage constants
@@ -43,7 +43,7 @@ public struct ImageDetail: Codable {
extension ClientImage {
public func details() async throws -> ImageDetail {
let indexDescriptor = self.descriptor
let descriptor = try await self.resolved()
let reference = self.reference
var variants: [ImageDetail.Variants] = []
for desc in try await self.index().manifests {
@@ -61,6 +61,6 @@ extension ClientImage {
let size = desc.size + manifest.config.size + manifest.layers.reduce(0, { (l, r) in l + r.size })
variants.append(.init(platform: platform, size: size, config: config))
}
return ImageDetail(name: reference, index: indexDescriptor, variants: variants)
return ImageDetail(name: reference, index: descriptor, variants: variants)
}
}