Skip to content

Commit 3f92229

Browse files
committed
ContainerRegistry: Add putIndex
1 parent a6966f5 commit 3f92229

File tree

3 files changed

+91
-4
lines changed

3 files changed

+91
-4
lines changed

Sources/ContainerRegistry/ImageDestination.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,25 @@ public protocol ImageDestination {
8282
reference: (any ImageReference.Reference)?,
8383
manifest: ImageManifest
8484
) async throws -> ContentDescriptor
85+
86+
/// Encodes and uploads an image index.
87+
///
88+
/// - Parameters:
89+
/// - repository: Name of the destination repository.
90+
/// - reference: Optional tag to apply to this index.
91+
/// - index: Index to be uploaded.
92+
/// - Returns: An ContentDescriptor object representing the
93+
/// uploaded index.
94+
/// - Throws: If the index cannot be encoded or the upload fails.
95+
///
96+
/// An index is a type of manifest. Manifests are not treated as blobs
97+
/// by the distribution specification. They have their own MIME types
98+
/// and are uploaded to different endpoint.
99+
func putIndex(
100+
repository: ImageReference.Repository,
101+
reference: (any ImageReference.Reference)?,
102+
index: ImageIndex
103+
) async throws -> ContentDescriptor
85104
}
86105

87106
extension ImageDestination {

Sources/ContainerRegistry/RegistryClient+ImageDestination.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ extension RegistryClient: ImageDestination {
144144
/// - reference: Optional tag to apply to this manifest.
145145
/// - manifest: Manifest to be uploaded.
146146
/// - Returns: An ContentDescriptor object representing the
147-
/// uploaded blob.
148-
/// - Throws: If the blob cannot be encoded or the upload fails.
147+
/// uploaded manifest.
148+
/// - Throws: If the manifest cannot be encoded or the upload fails.
149149
///
150150
/// Manifests are not treated as blobs by the distribution specification.
151151
/// They have their own MIME types and are uploaded to different
@@ -177,4 +177,46 @@ extension RegistryClient: ImageDestination {
177177
size: Int64(encoded.count)
178178
)
179179
}
180+
181+
/// Encodes and uploads an image index.
182+
///
183+
/// - Parameters:
184+
/// - repository: Name of the destination repository.
185+
/// - reference: Optional tag to apply to this index.
186+
/// - index: Index to be uploaded.
187+
/// - Returns: An ContentDescriptor object representing the
188+
/// uploaded index.
189+
/// - Throws: If the index cannot be encoded or the upload fails.
190+
///
191+
/// An index is a type of manifest. Manifests are not treated as blobs
192+
/// by the distribution specification. They have their own MIME types
193+
/// and are uploaded to different endpoint.
194+
public func putIndex(
195+
repository: ImageReference.Repository,
196+
reference: (any ImageReference.Reference)? = nil,
197+
index: ImageIndex
198+
) async throws -> ContentDescriptor {
199+
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
200+
201+
let encoded = try encoder.encode(index)
202+
let digest = ImageReference.Digest(of: encoded)
203+
let mediaType = index.mediaType ?? "application/vnd.oci.image.index.v1+json"
204+
205+
let _ = try await executeRequestThrowing(
206+
.put(
207+
repository,
208+
path: "manifests/\(reference ?? digest)",
209+
contentType: mediaType
210+
),
211+
uploading: encoded,
212+
expectingStatus: .created,
213+
decodingErrors: [.notFound]
214+
)
215+
216+
return ContentDescriptor(
217+
mediaType: mediaType,
218+
digest: "\(digest)",
219+
size: Int64(encoded.count)
220+
)
221+
}
180222
}

Sources/containertool/Extensions/RegistryClient+publish.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,40 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
146146
log("manifest: \(manifestDescriptor.digest) (\(manifestDescriptor.size) bytes)")
147147
}
148148

149-
// Use the manifest's digest if the user did not provide a human-readable tag
149+
// MARK: Create application index
150+
151+
let index = ImageIndex(
152+
schemaVersion: 2,
153+
mediaType: "application/vnd.oci.image.index.v1+json",
154+
manifests: [
155+
ContentDescriptor(
156+
mediaType: manifestDescriptor.mediaType,
157+
digest: manifestDescriptor.digest,
158+
size: Int64(manifestDescriptor.size)
159+
)
160+
]
161+
)
162+
163+
// MARK: Upload application manifest
164+
165+
let indexDescriptor = try await destination.putIndex(
166+
repository: destinationImage.repository,
167+
reference: destinationImage.reference,
168+
index: index
169+
)
170+
171+
if verbose {
172+
log("index: \(indexDescriptor.digest) (\(indexDescriptor.size) bytes)")
173+
}
174+
175+
// Use the index digest if the user did not provide a human-readable tag
150176
// To support multiarch images, we should also create an an index pointing to
151177
// this manifest.
152178
let reference: ImageReference.Reference
153179
if let tag {
154180
reference = try ImageReference.Tag(tag)
155181
} else {
156-
reference = try ImageReference.Digest(manifestDescriptor.digest)
182+
reference = try ImageReference.Digest(indexDescriptor.digest)
157183
}
158184

159185
var result = destinationImage

0 commit comments

Comments
 (0)