Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,30 @@
extension RegistryClient {
/// Get an image configuration record from the registry.
/// - Parameters:
/// - repository: Name of the repository containing the record.
/// - image: Reference to the image containing the record.
/// - digest: Digest of the record.
/// - Returns: The image confguration record stored in `repository` with digest `digest`.
/// - Throws: If the blob cannot be decoded as an `ImageConfiguration`.
///
/// 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.
public func getImageConfiguration(repository: String, digest: String) async throws -> ImageConfiguration {
try await getBlob(repository: repository, digest: digest)
public func getImageConfiguration(forImage image: ImageReference, digest: String) async throws -> ImageConfiguration
{
try await getBlob(repository: image.repository, digest: digest)
}

/// Upload an image configuration record to the registry.
/// - Parameters:
/// - repository: Name of the repository in which to store the record.
/// - image: Reference to the image associated with the record.
/// - configuration: An image configuration record
/// - Returns: An `ContentDescriptor` referring to the blob stored in the registry.
/// - Throws: If the blob upload fails.
///
/// Image configuration records are stored as blobs in the registry. This function encodes the provided configuration record and stores it as a blob in the registry.
public func putImageConfiguration(repository: String, configuration: ImageConfiguration) async throws
public func putImageConfiguration(forImage image: ImageReference, configuration: ImageConfiguration) async throws
-> ContentDescriptor
{
try await putBlob(
repository: repository,
repository: image.repository,
mediaType: "application/vnd.oci.image.config.v1+json",
data: configuration
)
Expand Down
8 changes: 4 additions & 4 deletions Sources/containertool/Extensions/RegistryClient+Layers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ import struct Foundation.Data
import ContainerRegistry

extension RegistryClient {
func getImageManifest(repository: String, reference: String, architecture: String) async throws -> ImageManifest {
func getImageManifest(forImage image: ImageReference, architecture: String) async throws -> ImageManifest {
// We pushed the amd64 tag but it points to a single-architecture index, not directly to a manifest
// if we get an index we should get a manifest, otherwise we might get a manifest directly

do {
// Try to retrieve a manifest. If the object with this reference is actually an index, the content-type will not match and
// an error will be thrown.
return try await getManifest(repository: repository, reference: reference)
return try await getManifest(repository: image.repository, reference: image.reference)
} catch {
// Try again, treating the top level object as an index.
// This could be more efficient if the exception thrown by getManfiest() included the data it was unable to parse
let index = try await getIndex(repository: repository, reference: reference)
let index = try await getIndex(repository: image.repository, reference: image.reference)
guard let manifest = index.manifests.first(where: { $0.platform?.architecture == architecture }) else {
throw "Could not find a suitable base image for \(architecture)"
}
// The index should not point to another index; if it does, this call will throw a final error to be handled by the caller.
return try await getManifest(repository: repository, reference: manifest.digest)
return try await getManifest(repository: image.repository, reference: manifest.digest)
}
}

Expand Down
7 changes: 3 additions & 4 deletions Sources/containertool/containertool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,13 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
let baseimage_config: ImageConfiguration
if let source {
baseimage_manifest = try await source.getImageManifest(
repository: baseimage.repository,
reference: baseimage.reference,
forImage: baseimage,
architecture: architecture
)
log("Found base image manifest: \(baseimage_manifest.digest)")

baseimage_config = try await source.getImageConfiguration(
repository: baseimage.repository,
forImage: baseimage,
digest: baseimage_manifest.config.digest
)
log("Found base image configuration: \(baseimage_manifest.config.digest)")
Expand Down Expand Up @@ -204,7 +203,7 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
)

let config_blob = try await destination.putImageConfiguration(
repository: destination_image.repository,
forImage: destination_image,
configuration: configuration
)

Expand Down
5 changes: 3 additions & 2 deletions Tests/ContainerRegistryTests/SmokeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ struct SmokeTests {

@Test func testPutAndGetImageConfiguration() async throws {
let repository = "testputandgetimageconfiguration" // repository name must be lowercase
let image = ImageReference(registry: "registry", repository: repository, reference: "latest")

let configuration = ImageConfiguration(
created: "1996-12-19T16:39:57-08:00",
Expand All @@ -195,12 +196,12 @@ struct SmokeTests {
history: [.init(created: "1996-12-19T16:39:57-08:00", author: "test", created_by: "smoketest")]
)
let config_descriptor = try await client.putImageConfiguration(
repository: repository,
forImage: image,
configuration: configuration
)

let downloaded = try await client.getImageConfiguration(
repository: repository,
forImage: image,
digest: config_descriptor.digest
)

Expand Down