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
19 changes: 19 additions & 0 deletions Sources/ContainerRegistry/ImageDestination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ public protocol ImageDestination {
reference: (any ImageReference.Reference)?,
manifest: ImageManifest
) async throws -> ContentDescriptor

/// Encodes and uploads an image index.
///
/// - Parameters:
/// - repository: Name of the destination repository.
/// - reference: Optional tag to apply to this index.
/// - index: Index to be uploaded.
/// - Returns: An ContentDescriptor object representing the
/// uploaded index.
/// - Throws: If the index cannot be encoded or the upload fails.
///
/// An index is a type of manifest. Manifests are not treated as blobs
/// by the distribution specification. They have their own MIME types
/// and are uploaded to different endpoint.
func putIndex(
repository: ImageReference.Repository,
reference: (any ImageReference.Reference)?,
index: ImageIndex
) async throws -> ContentDescriptor
}

extension ImageDestination {
Expand Down
46 changes: 44 additions & 2 deletions Sources/ContainerRegistry/RegistryClient+ImageDestination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ extension RegistryClient: ImageDestination {
/// - reference: Optional tag to apply to this manifest.
/// - manifest: Manifest to be uploaded.
/// - Returns: An ContentDescriptor object representing the
/// uploaded blob.
/// - Throws: If the blob cannot be encoded or the upload fails.
/// uploaded manifest.
/// - Throws: If the manifest cannot be encoded or the upload fails.
///
/// Manifests are not treated as blobs by the distribution specification.
/// They have their own MIME types and are uploaded to different
Expand Down Expand Up @@ -177,4 +177,46 @@ extension RegistryClient: ImageDestination {
size: Int64(encoded.count)
)
}

/// Encodes and uploads an image index.
///
/// - Parameters:
/// - repository: Name of the destination repository.
/// - reference: Optional tag to apply to this index.
/// - index: Index to be uploaded.
/// - Returns: An ContentDescriptor object representing the
/// uploaded index.
/// - Throws: If the index cannot be encoded or the upload fails.
///
/// An index is a type of manifest. Manifests are not treated as blobs
/// by the distribution specification. They have their own MIME types
/// and are uploaded to different endpoint.
public func putIndex(
repository: ImageReference.Repository,
reference: (any ImageReference.Reference)? = nil,
index: ImageIndex
) async throws -> ContentDescriptor {
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests

let encoded = try encoder.encode(index)
let digest = ImageReference.Digest(of: encoded)
let mediaType = index.mediaType ?? "application/vnd.oci.image.index.v1+json"

let _ = try await executeRequestThrowing(
.put(
repository,
path: "manifests/\(reference ?? digest)",
contentType: mediaType
),
uploading: encoded,
expectingStatus: .created,
decodingErrors: [.notFound]
)

return ContentDescriptor(
mediaType: mediaType,
digest: "\(digest)",
size: Int64(encoded.count)
)
}
}
31 changes: 29 additions & 2 deletions Sources/containertool/Extensions/RegistryClient+publish.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,41 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
log("manifest: \(manifestDescriptor.digest) (\(manifestDescriptor.size) bytes)")
}

// Use the manifest's digest if the user did not provide a human-readable tag
// MARK: Create application index

let index = ImageIndex(
schemaVersion: 2,
mediaType: "application/vnd.oci.image.index.v1+json",
manifests: [
ContentDescriptor(
mediaType: manifestDescriptor.mediaType,
digest: manifestDescriptor.digest,
size: Int64(manifestDescriptor.size),
platform: .init(architecture: architecture, os: os)
)
]
)

// MARK: Upload application manifest

let indexDescriptor = try await destination.putIndex(
repository: destinationImage.repository,
reference: destinationImage.reference,
index: index
)

if verbose {
log("index: \(indexDescriptor.digest) (\(indexDescriptor.size) bytes)")
}

// Use the index digest if the user did not provide a human-readable tag
// To support multiarch images, we should also create an an index pointing to
// this manifest.
let reference: ImageReference.Reference
if let tag {
reference = try ImageReference.Tag(tag)
} else {
reference = try ImageReference.Digest(manifestDescriptor.digest)
reference = try ImageReference.Digest(indexDescriptor.digest)
}

var result = destinationImage
Expand Down
4 changes: 2 additions & 2 deletions scripts/test-containertool-elf-detection.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ FILETYPE=$(file "$PKGPATH/.build/x86_64-swift-linux-musl/debug/hello")
log "Executable type: $FILETYPE"

IMGREF=$(swift run containertool --repository localhost:5000/elf_test "$PKGPATH/.build/x86_64-swift-linux-musl/debug/hello" --from scratch)
$RUNTIME pull "$IMGREF"
$RUNTIME pull --platform=linux/amd64 "$IMGREF"
IMGARCH=$($RUNTIME inspect "$IMGREF" --format "{{.Architecture}}")
if [ "$IMGARCH" = "amd64" ] ; then
log "x86_64 detection: PASSED"
Expand All @@ -61,7 +61,7 @@ FILETYPE=$(file "$PKGPATH/.build/x86_64-swift-linux-musl/debug/hello")
log "Executable type: $FILETYPE"

IMGREF=$(swift run containertool --repository localhost:5000/elf_test "$PKGPATH/.build/aarch64-swift-linux-musl/debug/hello" --from scratch)
$RUNTIME pull "$IMGREF"
$RUNTIME pull --platform=linux/arm64 "$IMGREF"
IMGARCH=$($RUNTIME inspect "$IMGREF" --format "{{.Architecture}}")
if [ "$IMGARCH" = "arm64" ] ; then
log "aarch64 detection: PASSED"
Expand Down