Skip to content

Commit ef42225

Browse files
committed
ContainerRegistry: Define ImageSource protocol
1 parent c60a66a commit ef42225

File tree

6 files changed

+179
-40
lines changed

6 files changed

+179
-40
lines changed

Sources/ContainerRegistry/Blobs.swift

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 struct Foundation.Data
16+
17+
/// A source, such as a registry, from which container images can be fetched.
18+
public protocol ImageSource {
19+
/// Fetches a blob of unstructured data.
20+
///
21+
/// - Parameters:
22+
/// - repository: Name of the source repository.
23+
/// - digest: Digest of the blob.
24+
/// - Returns: The downloaded data.
25+
/// - Throws: If the blob download fails.
26+
func getBlob(
27+
repository: ImageReference.Repository,
28+
digest: ImageReference.Digest
29+
) async throws -> Data
30+
31+
/// Fetches an image manifest.
32+
///
33+
/// - Parameters:
34+
/// - repository: Name of the source repository.
35+
/// - reference: Tag or digest of the manifest to fetch.
36+
/// - Returns: The downloaded manifest.
37+
/// - Throws: If the download fails or the manifest cannot be decoded.
38+
func getManifest(
39+
repository: ImageReference.Repository,
40+
reference: any ImageReference.Reference
41+
) async throws -> (ImageManifest, ContentDescriptor)
42+
43+
/// Fetches an image index.
44+
///
45+
/// - Parameters:
46+
/// - repository: Name of the source repository.
47+
/// - reference: Tag or digest of the index to fetch.
48+
/// - Returns: The downloaded index.
49+
/// - Throws: If the download fails or the index cannot be decoded.
50+
func getIndex(
51+
repository: ImageReference.Repository,
52+
reference: any ImageReference.Reference
53+
) async throws -> ImageIndex
54+
55+
/// Fetches an image configuration from the registry.
56+
///
57+
/// - Parameters:
58+
/// - image: Reference to the image containing the record.
59+
/// - digest: Digest of the configuration object to fetch.
60+
/// - Returns: The image confguration record.
61+
/// - Throws: If the download fails or the configuration record cannot be decoded.
62+
///
63+
/// Image configuration records are stored as blobs in the registry. This function retrieves
64+
/// the requested blob and tries to decode it as a configuration record.
65+
func getImageConfiguration(
66+
forImage image: ImageReference,
67+
digest: ImageReference.Digest
68+
) async throws -> ImageConfiguration
69+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftContainerPlugin open source project
4+
//
5+
// Copyright (c) 2024 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 struct Foundation.Data
16+
17+
extension RegistryClient: ImageSource {
18+
/// Fetches an unstructured blob of data from the registry.
19+
///
20+
/// - Parameters:
21+
/// - repository: Name of the repository containing the blob.
22+
/// - digest: Digest of the blob.
23+
/// - Returns: The downloaded data.
24+
/// - Throws: If the blob download fails.
25+
public func getBlob(
26+
repository: ImageReference.Repository,
27+
digest: ImageReference.Digest
28+
) async throws -> Data {
29+
try await executeRequestThrowing(
30+
.get(repository, path: "blobs/\(digest)", accepting: ["application/octet-stream"]),
31+
decodingErrors: [.notFound]
32+
)
33+
.data
34+
}
35+
36+
public func getManifest(
37+
repository: ImageReference.Repository,
38+
reference: any ImageReference.Reference
39+
) async throws -> (ImageManifest, ContentDescriptor) {
40+
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
41+
let (data, response) = try await executeRequestThrowing(
42+
.get(
43+
repository,
44+
path: "manifests/\(reference)",
45+
accepting: [
46+
"application/vnd.oci.image.manifest.v1+json",
47+
"application/vnd.docker.distribution.manifest.v2+json",
48+
]
49+
),
50+
decodingErrors: [.notFound]
51+
)
52+
return (
53+
try decoder.decode(ImageManifest.self, from: data),
54+
ContentDescriptor(
55+
mediaType: response.headerFields[.contentType] ?? "application/vnd.oci.image.manifest.v1+json",
56+
digest: "\(ImageReference.Digest(of: data))",
57+
size: Int64(data.count)
58+
)
59+
)
60+
}
61+
62+
public func getIndex(
63+
repository: ImageReference.Repository,
64+
reference: any ImageReference.Reference
65+
) async throws -> ImageIndex {
66+
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
67+
let (data, _) = try await executeRequestThrowing(
68+
.get(
69+
repository,
70+
path: "manifests/\(reference)",
71+
accepting: [
72+
"application/vnd.oci.image.index.v1+json",
73+
"application/vnd.docker.distribution.manifest.list.v2+json",
74+
]
75+
),
76+
decodingErrors: [.notFound]
77+
)
78+
return try decoder.decode(ImageIndex.self, from: data)
79+
}
80+
81+
/// Get an image configuration record from the registry.
82+
/// - Parameters:
83+
/// - image: Reference to the image containing the record.
84+
/// - digest: Digest of the record.
85+
/// - Returns: The image confguration record stored in `repository` with digest `digest`.
86+
/// - Throws: If the blob cannot be decoded as an `ImageConfiguration`.
87+
///
88+
/// Image configuration records are stored as blobs in the registry. This function retrieves the requested blob and tries to decode it as a configuration record.
89+
public func getImageConfiguration(
90+
forImage image: ImageReference,
91+
digest: ImageReference.Digest
92+
) async throws -> ImageConfiguration {
93+
let data = try await getBlob(repository: image.repository, digest: digest)
94+
return try decoder.decode(ImageConfiguration.self, from: data)
95+
}
96+
}

Sources/containertool/Extensions/RegistryClient+CopyBlobs.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import ContainerRegistry
1616

17-
extension RegistryClient {
17+
extension ImageSource {
1818
/// Copies a blob from another registry to this one.
1919
/// - Parameters:
2020
/// - digest: The digest of the blob to copy.

Sources/containertool/Extensions/RegistryClient+Layers.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@
1515
import struct Foundation.Data
1616
import ContainerRegistry
1717

18-
extension RegistryClient {
19-
func getImageManifest(forImage image: ImageReference, architecture: String) async throws -> (
20-
ImageManifest, ContentDescriptor
21-
) {
18+
extension ImageSource {
19+
// A single-architecture image may begin with a manifest; a multi-architecture
20+
// image begins with an index which points to one or more manifests. The client
21+
// could receive either type of object:
22+
// * if the registry sends a manifest, this function returns it
23+
// * if the registry sends an index, this function chooses the
24+
// appropriate architecture-specific manifest and returns it.
25+
func getImageManifest(
26+
forImage image: ImageReference,
27+
architecture: String
28+
) async throws -> (ImageManifest, ContentDescriptor) {
2229
do {
2330
// Try to retrieve a manifest. If the object with this reference is actually an index, the content-type will not match and
2431
// an error will be thrown.

Sources/containertool/Extensions/RegistryClient+publish.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import struct Foundation.URL
1818
import ContainerRegistry
1919
import Tar
2020

21-
func publishContainerImage<Destination: ImageDestination>(
21+
func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
2222
baseImage: ImageReference,
2323
destinationImage: ImageReference,
24-
source: RegistryClient?,
24+
source: Source?,
2525
destination: Destination,
2626
architecture: String,
2727
os: String,

0 commit comments

Comments
 (0)