Skip to content

Commit 935912e

Browse files
authored
ContainerRegistry: Make digest(of:Data) an ImageReference.Digest constructor (#147)
Motivation ---------- The `digest(of:)` function calculates an image digest from a `Data` object. Previously the digest was a string and the function stood alone, but after the introduction of the `ImageReference.Digest` type it is logical for it to be an alternative constructor. Modifications ------------- `digest(of:)` is now `ImageReference.Digest(of:)` Result ------ Refactoring. No functional change. Test Plan --------- All existing tests continue to pass.
1 parent d897e9a commit 935912e

File tree

5 files changed

+53
-48
lines changed

5 files changed

+53
-48
lines changed

Sources/ContainerRegistry/Blobs.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public extension RegistryClient {
9797
// Append the digest to the upload location, as the specification requires.
9898
// The server's URL is arbitrary and might already contain query items which we must not overwrite.
9999
// The URL could even point to a different host.
100-
let digest = digest(of: data)
100+
let digest = ImageReference.Digest(of: data)
101101
let uploadURL = location.appending(queryItems: [.init(name: "digest", value: "\(digest)")])
102102

103103
let httpResponse = try await executeRequestThrowing(
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftContainerPlugin open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftContainerPlugin project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftContainerPlugin project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import HTTPTypes
17+
import struct Crypto.SHA256
18+
import struct Crypto.SHA512
19+
20+
// The distribution spec says that Docker- prefix headers are no longer to be used,
21+
// but also specifies that the registry digest is returned in this header.
22+
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
23+
extension HTTPField.Name {
24+
static let dockerContentDigest = Self("Docker-Content-Digest")!
25+
}
26+
27+
extension ImageReference.Digest {
28+
/// Calculate the digest of a blob of data.
29+
/// - Parameters:
30+
/// - data: Blob of data to digest.
31+
/// - algorithm: Digest algorithm to use.
32+
public init(
33+
of data: any DataProtocol,
34+
algorithm: ImageReference.Digest.Algorithm = .sha256
35+
) {
36+
// SHA256 is required; some registries might also support SHA512
37+
switch algorithm {
38+
case .sha256:
39+
let hash = SHA256.hash(data: data)
40+
let digest = hash.compactMap { String(format: "%02x", $0) }.joined()
41+
try! self.init("sha256:" + digest)
42+
43+
case .sha512:
44+
let hash = SHA512.hash(data: data)
45+
let digest = hash.compactMap { String(format: "%02x", $0) }.joined()
46+
try! self.init("sha512:" + digest)
47+
}
48+
}
49+
}

Sources/ContainerRegistry/Manifests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public extension RegistryClient {
2121
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
2222

2323
let encoded = try encoder.encode(manifest)
24-
let digest = digest(of: encoded)
24+
let digest = ImageReference.Digest(of: encoded)
2525
let mediaType = manifest.mediaType ?? "application/vnd.oci.image.manifest.v1+json"
2626

2727
let _ = try await executeRequestThrowing(
@@ -62,7 +62,7 @@ public extension RegistryClient {
6262
try decoder.decode(ImageManifest.self, from: data),
6363
ContentDescriptor(
6464
mediaType: response.headerFields[.contentType] ?? "application/vnd.oci.image.manifest.v1+json",
65-
digest: "\(digest(of: data))",
65+
digest: "\(ImageReference.Digest(of: data))",
6666
size: Int64(data.count)
6767
)
6868
)

Sources/ContainerRegistry/RegistryClient+Digest.swift

Lines changed: 0 additions & 44 deletions
This file was deleted.

Sources/containertool/Extensions/RegistryClient+Layers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ extension RegistryClient {
5252
mediaType: String = "application/vnd.oci.image.layer.v1.tar+gzip"
5353
) async throws -> ImageLayer {
5454
// The diffID is the hash of the unzipped layer tarball
55-
let diffID = digest(of: contents)
55+
let diffID = ImageReference.Digest(of: contents)
5656
// The layer blob is the gzipped tarball; the descriptor is the hash of this gzipped blob
5757
let blob = Data(gzip(contents))
5858
let descriptor = try await putBlob(repository: repository, mediaType: mediaType, data: blob)

0 commit comments

Comments
 (0)