Skip to content

Commit 429235b

Browse files
committed
ContainerRegistry: Define ImageSource protocol
1 parent 3f34032 commit 429235b

File tree

6 files changed

+193
-40
lines changed

6 files changed

+193
-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: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
/// Fetches an image manifest.
37+
///
38+
/// - Parameters:
39+
/// - repository: Name of the source repository.
40+
/// - reference: Tag or digest of the manifest to fetch.
41+
/// - Returns: The downloaded manifest.
42+
/// - Throws: If the download fails or the manifest cannot be decoded.
43+
public func getManifest(
44+
repository: ImageReference.Repository,
45+
reference: any ImageReference.Reference
46+
) async throws -> (ImageManifest, ContentDescriptor) {
47+
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
48+
let (data, response) = try await executeRequestThrowing(
49+
.get(
50+
repository,
51+
path: "manifests/\(reference)",
52+
accepting: [
53+
"application/vnd.oci.image.manifest.v1+json",
54+
"application/vnd.docker.distribution.manifest.v2+json",
55+
]
56+
),
57+
decodingErrors: [.notFound]
58+
)
59+
return (
60+
try decoder.decode(ImageManifest.self, from: data),
61+
ContentDescriptor(
62+
mediaType: response.headerFields[.contentType] ?? "application/vnd.oci.image.manifest.v1+json",
63+
digest: "\(ImageReference.Digest(of: data))",
64+
size: Int64(data.count)
65+
)
66+
)
67+
}
68+
69+
/// Fetches an image index.
70+
///
71+
/// - Parameters:
72+
/// - repository: Name of the source repository.
73+
/// - reference: Tag or digest of the index to fetch.
74+
/// - Returns: The downloaded index.
75+
/// - Throws: If the download fails or the index cannot be decoded.
76+
public func getIndex(
77+
repository: ImageReference.Repository,
78+
reference: any ImageReference.Reference
79+
) async throws -> ImageIndex {
80+
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
81+
let (data, _) = try await executeRequestThrowing(
82+
.get(
83+
repository,
84+
path: "manifests/\(reference)",
85+
accepting: [
86+
"application/vnd.oci.image.index.v1+json",
87+
"application/vnd.docker.distribution.manifest.list.v2+json",
88+
]
89+
),
90+
decodingErrors: [.notFound]
91+
)
92+
return try decoder.decode(ImageIndex.self, from: data)
93+
}
94+
95+
/// Get an image configuration record from the registry.
96+
/// - Parameters:
97+
/// - image: Reference to the image containing the record.
98+
/// - digest: Digest of the record.
99+
/// - Returns: The image confguration record stored in `repository` with digest `digest`.
100+
/// - Throws: If the blob cannot be decoded as an `ImageConfiguration`.
101+
///
102+
/// 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.
103+
public func getImageConfiguration(
104+
forImage image: ImageReference,
105+
digest: ImageReference.Digest
106+
) async throws -> ImageConfiguration {
107+
let data = try await getBlob(repository: image.repository, digest: digest)
108+
return try decoder.decode(ImageConfiguration.self, from: data)
109+
}
110+
}

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)