Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/ContainerRegistry/Blobs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public extension RegistryClient {
// Append the digest to the upload location, as the specification requires.
// The server's URL is arbitrary and might already contain query items which we must not overwrite.
// The URL could even point to a different host.
let digest = digest(of: data)
let digest = ImageReference.Digest(of: data)
let uploadURL = location.appending(queryItems: [.init(name: "digest", value: "\(digest)")])

let httpResponse = try await executeRequestThrowing(
Expand Down
49 changes: 49 additions & 0 deletions Sources/ContainerRegistry/ImageReference+Digest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftContainerPlugin open source project
//
// Copyright (c) 2025 Apple Inc. and the SwiftContainerPlugin project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftContainerPlugin project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation
import HTTPTypes
import struct Crypto.SHA256
import struct Crypto.SHA512

// The distribution spec says that Docker- prefix headers are no longer to be used,
// but also specifies that the registry digest is returned in this header.
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
extension HTTPField.Name {
static let dockerContentDigest = Self("Docker-Content-Digest")!
}

extension ImageReference.Digest {
/// Calculate the digest of a blob of data.
/// - Parameters:
/// - data: Blob of data to digest.
/// - algorithm: Digest algorithm to use.
public init(
of data: any DataProtocol,
algorithm: ImageReference.Digest.Algorithm = .sha256
) {
// SHA256 is required; some registries might also support SHA512
switch algorithm {
case .sha256:
let hash = SHA256.hash(data: data)
let digest = hash.compactMap { String(format: "%02x", $0) }.joined()
try! self.init("sha256:" + digest)

case .sha512:
let hash = SHA512.hash(data: data)
let digest = hash.compactMap { String(format: "%02x", $0) }.joined()
try! self.init("sha512:" + digest)
}
}
}
4 changes: 2 additions & 2 deletions Sources/ContainerRegistry/Manifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public extension RegistryClient {
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests

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

let _ = try await executeRequestThrowing(
Expand Down Expand Up @@ -62,7 +62,7 @@ public extension RegistryClient {
try decoder.decode(ImageManifest.self, from: data),
ContentDescriptor(
mediaType: response.headerFields[.contentType] ?? "application/vnd.oci.image.manifest.v1+json",
digest: "\(digest(of: data))",
digest: "\(ImageReference.Digest(of: data))",
size: Int64(data.count)
)
)
Expand Down
44 changes: 0 additions & 44 deletions Sources/ContainerRegistry/RegistryClient+Digest.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ extension RegistryClient {
mediaType: String = "application/vnd.oci.image.layer.v1.tar+gzip"
) async throws -> ImageLayer {
// The diffID is the hash of the unzipped layer tarball
let diffID = digest(of: contents)
let diffID = ImageReference.Digest(of: contents)
// The layer blob is the gzipped tarball; the descriptor is the hash of this gzipped blob
let blob = Data(gzip(contents))
let descriptor = try await putBlob(repository: repository, mediaType: mediaType, data: blob)
Expand Down