Skip to content

Commit 7719846

Browse files
committed
ContainerRegistry: Return a ContentDescriptor from putManifest()
Returning a ContentDescriptor provides the information needed to add the manifest to an image index, making it possible to build multi-architecture images.
1 parent 5430d5e commit 7719846

File tree

5 files changed

+30
-57
lines changed

5 files changed

+30
-57
lines changed

Sources/ContainerRegistry/ImageManifest+Digest.swift

Lines changed: 0 additions & 26 deletions
This file was deleted.

Sources/ContainerRegistry/Manifests.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,31 @@
1515
public extension RegistryClient {
1616
func putManifest(
1717
repository: ImageReference.Repository,
18-
reference: any ImageReference.Reference,
18+
reference: (any ImageReference.Reference)? = nil,
1919
manifest: ImageManifest
20-
) async throws -> String {
20+
) async throws -> ContentDescriptor {
2121
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
22-
let httpResponse = try await executeRequestThrowing(
23-
// All blob uploads have Content-Type: application/octet-stream on the wire, even if mediatype is different
22+
23+
let encoded = try encoder.encode(manifest)
24+
let digest = digest(of: encoded)
25+
let mediaType = manifest.mediaType ?? "application/vnd.oci.image.manifest.v1+json"
26+
27+
let _ = try await executeRequestThrowing(
2428
.put(
2529
repository,
26-
path: "manifests/\(reference)",
27-
contentType: manifest.mediaType ?? "application/vnd.oci.image.manifest.v1+json"
30+
path: "manifests/\(reference ?? digest)",
31+
contentType: mediaType
2832
),
29-
uploading: manifest,
33+
uploading: encoded,
3034
expectingStatus: .created,
3135
decodingErrors: [.notFound]
3236
)
3337

34-
// The distribution spec says the response MUST contain a Location header
35-
// providing a URL from which the saved manifest can be downloaded.
36-
// However some registries return URLs which cannot be fetched, and
37-
// ECR does not set this header at all.
38-
// If the header is not present, create a suitable value.
39-
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests
40-
return httpResponse.response.headerFields[.location]
41-
?? registryURL.distributionEndpoint(forRepository: repository, andEndpoint: "manifests/\(manifest.digest)")
42-
.absoluteString
38+
return ContentDescriptor(
39+
mediaType: mediaType,
40+
digest: "\(digest)",
41+
size: Int64(encoded.count)
42+
)
4343
}
4444

4545
func getManifest(

Sources/containertool/Extensions/RegistryClient+Layers.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ extension RegistryClient {
1919
func getImageManifest(forImage image: ImageReference, architecture: String) async throws -> (
2020
ImageManifest, ContentDescriptor
2121
) {
22-
// We pushed the amd64 tag but it points to a single-architecture index, not directly to a manifest
23-
// if we get an index we should get a manifest, otherwise we might get a manifest directly
24-
2522
do {
2623
// Try to retrieve a manifest. If the object with this reference is actually an index, the content-type will not match and
2724
// an error will be thrown.

Sources/containertool/containertool.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ extension RegistryClient {
255255
forImage: baseImage,
256256
architecture: architecture
257257
)
258-
log("Found base image manifest: \(baseImageDescriptor.digest)")
258+
try log("Found base image manifest: \(ImageReference.Digest(baseImageDescriptor.digest))")
259259

260260
baseImageConfiguration = try await source.getImageConfiguration(
261261
forImage: baseImage,
@@ -369,22 +369,25 @@ extension RegistryClient {
369369

370370
// MARK: Upload application manifest
371371

372+
let manifestDescriptor = try await self.putManifest(
373+
repository: destinationImage.repository,
374+
reference: destinationImage.reference,
375+
manifest: manifest
376+
)
377+
378+
if verbose {
379+
log("manifest: \(manifestDescriptor.digest) (\(manifestDescriptor.size) bytes)")
380+
}
381+
372382
// Use the manifest's digest if the user did not provide a human-readable tag
373383
// To support multiarch images, we should also create an an index pointing to
374384
// this manifest.
375385
let reference: ImageReference.Reference
376386
if let tag {
377387
reference = try ImageReference.Tag(tag)
378388
} else {
379-
reference = manifest.digest
389+
reference = try ImageReference.Digest(manifestDescriptor.digest)
380390
}
381-
let location = try await self.putManifest(
382-
repository: destinationImage.repository,
383-
reference: destinationImage.reference,
384-
manifest: manifest
385-
)
386-
387-
if verbose { log(location) }
388391

389392
var result = destinationImage
390393
result.reference = reference

Tests/ContainerRegistryTests/SmokeTests.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,14 @@ struct SmokeTests {
184184
layers: [image_descriptor]
185185
)
186186

187-
let _ = try await client.putManifest(
187+
let descriptor = try await client.putManifest(
188188
repository: repository,
189-
reference: test_manifest.digest,
190189
manifest: test_manifest
191190
)
192191

193192
let (manifest, _) = try await client.getManifest(
194193
repository: repository,
195-
reference: test_manifest.digest
194+
reference: ImageReference.Digest(descriptor.digest)
196195
)
197196
#expect(manifest.schemaVersion == 2)
198197
#expect(manifest.config.mediaType == "application/vnd.docker.container.image.v1+json")

0 commit comments

Comments
 (0)