Skip to content

Commit 76b6f0f

Browse files
committed
Handle missing Docker-Content-Digest during manifest resolution
1 parent df125a2 commit 76b6f0f

1 file changed

Lines changed: 36 additions & 1 deletion

File tree

Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ extension RegistryClient {
5555
}
5656

5757
guard let digest = response.headers.first(name: "Docker-Content-Digest") else {
58-
throw ContainerizationError(.invalidArgument, message: "missing required header Docker-Content-Digest")
58+
return try await resolveByFetchingManifest(name: name, tag: tag, headers: headers)
5959
}
6060

6161
guard let type = response.headers.first(name: "Content-Type") else {
@@ -74,6 +74,41 @@ extension RegistryClient {
7474
}
7575
}
7676

77+
/// Resolve a root manifest descriptor by fetching the manifest when the registry does not
78+
/// include a Docker-Content-Digest header in the HEAD response.
79+
private func resolveByFetchingManifest(
80+
name: String,
81+
tag: String,
82+
headers: [(String, String)]
83+
) async throws -> Descriptor {
84+
var components = base
85+
components.path = "/v2/\(name)/manifests/\(tag)"
86+
87+
return try await request(components: components, headers: headers) { response in
88+
guard response.status == .ok else {
89+
let url = components.url?.absoluteString ?? "unknown"
90+
let reason = await ErrorResponse.fromResponseBody(response.body)?.jsonString
91+
throw Error.invalidStatus(url: url, response.status, reason: reason)
92+
}
93+
94+
guard let type = response.headers.first(name: "Content-Type") else {
95+
throw ContainerizationError(.invalidArgument, message: "missing required header Content-Type")
96+
}
97+
98+
let buffer = try await response.body.collect(upTo: .max)
99+
let data = Data(buffer.readableBytesView)
100+
101+
let computedDigest = SHA256.hash(data: data)
102+
let digestString = computedDigest.map { String(format: "%02x", $0) }.joined()
103+
104+
return Descriptor(
105+
mediaType: type,
106+
digest: "sha256:\(digestString)",
107+
size: Int64(data.count)
108+
)
109+
}
110+
}
111+
77112
/// Fetch resource (either manifest or blob) to memory with JSON decoding.
78113
public func fetch<T: Codable>(name: String, descriptor: Descriptor) async throws -> T {
79114
var components = base

0 commit comments

Comments
 (0)