diff --git a/Sources/CLI/Image/ImageList.swift b/Sources/CLI/Image/ImageList.swift index e666feca..bacf9cac 100644 --- a/Sources/CLI/Image/ImageList.swift +++ b/Sources/CLI/Image/ImageList.swift @@ -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 ?? "", - 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 ?? "", - Utility.trimDigest(digest: image.descriptor.digest), + Utility.trimDigest(digest: digest), ]) } let formatter = TableOutput(rows: rows) diff --git a/Sources/ContainerClient/Core/ClientImage.swift b/Sources/ContainerClient/Core/ClientImage.swift index 386bc118..401ca486 100644 --- a/Sources/ContainerClient/Core/ClientImage.swift +++ b/Sources/ContainerClient/Core/ClientImage.swift @@ -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 diff --git a/Sources/ContainerClient/Core/ImageDetail.swift b/Sources/ContainerClient/Core/ImageDetail.swift index 42562616..5096dfa0 100644 --- a/Sources/ContainerClient/Core/ImageDetail.swift +++ b/Sources/ContainerClient/Core/ImageDetail.swift @@ -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) } }