Skip to content

Commit 26ca3d6

Browse files
committed
ContainerRegistry: Add putIndex
1 parent a6966f5 commit 26ca3d6

File tree

3 files changed

+85
-4
lines changed

3 files changed

+85
-4
lines changed

Sources/ContainerRegistry/ImageDestination.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ public protocol ImageDestination {
8282
reference: (any ImageReference.Reference)?,
8383
manifest: ImageManifest
8484
) async throws -> ContentDescriptor
85+
86+
/// Saves an image index to the destination.
87+
/// - Parameters:
88+
/// - repository: Name of the destination repository.
89+
/// - reference: optional tag to apply to the manifest.
90+
/// - manifest: Manifest to be saved.
91+
/// - Returns: An ContentDescriptor object representing the
92+
/// saved blob.
93+
/// - Throws: If saving the index fails.
94+
func putIndex(
95+
repository: ImageReference.Repository,
96+
reference: (any ImageReference.Reference)?,
97+
index: ImageIndex
98+
) async throws -> ContentDescriptor
8599
}
86100

87101
extension ImageDestination {

Sources/ContainerRegistry/RegistryClient+ImageDestination.swift

Lines changed: 43 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,45 @@ 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 manifest.
186+
/// - manifest: Manifest 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+
/// Manifests are not treated as blobs by the distribution specification.
192+
/// They have their own MIME types and are uploaded to different
193+
public func putIndex(
194+
repository: ImageReference.Repository,
195+
reference: (any ImageReference.Reference)? = nil,
196+
index: ImageIndex
197+
) async throws -> ContentDescriptor {
198+
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
199+
200+
let encoded = try encoder.encode(index)
201+
let digest = ImageReference.Digest(of: encoded)
202+
let mediaType = index.mediaType ?? "application/vnd.oci.image.index.v1+json"
203+
204+
let _ = try await executeRequestThrowing(
205+
.put(
206+
repository,
207+
path: "manifests/\(reference ?? digest)",
208+
contentType: mediaType
209+
),
210+
uploading: encoded,
211+
expectingStatus: .created,
212+
decodingErrors: [.notFound]
213+
)
214+
215+
return ContentDescriptor(
216+
mediaType: mediaType,
217+
digest: "\(digest)",
218+
size: Int64(encoded.count)
219+
)
220+
}
180221
}

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)