containerization-framework 0.4.0

Rust bindings for Apple's Containerization framework: Linux containers in lightweight VMs, in-process and without a daemon.
//===----------------------------------------------------------------------===//
// Rust's values for images, content and unpacking read into Containerization's,
// and a `Platform` filled back into Rust's.
//===----------------------------------------------------------------------===//

import Containerization
import ContainerizationEXT4
import ContainerizationExtras
import ContainerizationOCI
import Foundation

// MARK: Rust to Swift

extension Platform {
  init(_ platform: RustPlatformRef) {
    self.init(
      arch: platform.architecture().toString(),
      os: platform.os().toString(),
      osVersion: platform.osVersion()?.toString(),
      osFeatures: platform.hasOsFeatures() ? strings(platform.osFeaturesLen(), platform.osFeaturesAt) : nil,
      variant: platform.variant()?.toString()
    )
  }
}

extension Descriptor {
  init(_ descriptor: RustDescriptorRef) {
    self.init(
      mediaType: descriptor.mediaType().toString(),
      digest: descriptor.digest().toString(),
      size: descriptor.size(),
      urls: descriptor.hasUrls() ? strings(descriptor.urlsLen(), descriptor.urlsAt) : nil,
      annotations: descriptor.hasAnnotations()
        ? dictionary(descriptor.annotationsLen(), descriptor.annotationKeyAt, descriptor.annotationValueAt) : nil,
      platform: descriptor.hasPlatform() ? Platform(descriptor.platform()) : nil,
      artifactType: descriptor.artifactType()?.toString()
    )
  }
}

extension Containerization.Image.Description {
  init(_ description: RustImageDescription) {
    self.init(reference: description.reference().toString(), descriptor: Descriptor(description.descriptor()))
  }
}

extension EXT4Unpacker {
  init(_ unpacker: RustExt4Unpacker) {
    var journal: EXT4.JournalConfig?
    if unpacker.hasJournal() {
      let mode = unpacker.hasJournalMode() ? EXT4.JournalConfig.JournalMode(unpacker.journalMode()) : nil
      journal = EXT4.JournalConfig(size: unpacker.journalSize(), defaultMode: mode)
    }

    self.init(capacityInBytes: unpacker.capacityInBytes(), journal: journal)
  }
}

extension EXT4.JournalConfig.JournalMode {
  init(_ kind: JournalModeKind) {
    self =
      switch kind {
      case .Writeback: .writeback
      case .Ordered: .ordered
      case .Journal: .journal
      }
  }
}

/// Rust's `ProgressHandler?`. Swift may call it from several threads at once,
/// and Rust's is `Sync`.
func progressHandler(_ handler: RustProgressHandler) -> ProgressHandler? {
  guard handler.isSome() else {
    return nil
  }
  nonisolated(unsafe) let handler = handler

  return { events in
    let kinds = RustVec<ProgressKind>()
    let values = RustVec<Int64>()
    for event in events {
      switch event {
      case .addItems(let value):
        kinds.push(value: .Items)
        values.push(value: Int64(value))
      case .addTotalItems(let value):
        kinds.push(value: .TotalItems)
        values.push(value: Int64(value))
      case .addSize(let value):
        kinds.push(value: .Size)
        values.push(value: value)
      case .addTotalSize(let value):
        kinds.push(value: .TotalSize)
        values.push(value: value)
      }
    }
    handler.call(kinds, values)
  }
}

// MARK: Swift to Rust

// The OCI image types, field by field: Rust builds its own. A field holding a
// list, a map, another of these types or an optional one of those comes back
// as an outcome of its own, holding `Absent` for `nil`.
extension CzOutcome {
  // `Descriptor`.

  func descriptorMediaType() -> String {
    (taken() as Descriptor).mediaType
  }

  func descriptorDigest() -> String {
    (taken() as Descriptor).digest
  }

  func descriptorSize() -> Int64 {
    (taken() as Descriptor).size
  }

  func descriptorUrls() -> CzOutcome {
    CzOutcome.holding((taken() as Descriptor).urls)
  }

  func descriptorAnnotations() -> CzOutcome {
    CzOutcome.holding((taken() as Descriptor).annotations)
  }

  func descriptorPlatform() -> CzOutcome {
    CzOutcome.holding((taken() as Descriptor).platform)
  }

  func descriptorArtifactType() -> String? {
    (taken() as Descriptor).artifactType
  }

  // `Index`.

  func indexSchemaVersion() -> Int {
    (taken() as Index).schemaVersion
  }

  func indexMediaType() -> String {
    (taken() as Index).mediaType
  }

  func indexManifests() -> CzOutcome {
    CzOutcome.holding((taken() as Index).manifests)
  }

  func indexAnnotations() -> CzOutcome {
    CzOutcome.holding((taken() as Index).annotations)
  }

  func indexSubject() -> CzOutcome {
    CzOutcome.holding((taken() as Index).subject)
  }

  func indexArtifactType() -> String? {
    (taken() as Index).artifactType
  }

  // `Manifest`.

  func manifestSchemaVersion() -> Int {
    (taken() as Manifest).schemaVersion
  }

  func manifestMediaType() -> String? {
    (taken() as Manifest).mediaType
  }

  func manifestConfig() -> CzOutcome {
    CzOutcome.holding((taken() as Manifest).config)
  }

  func manifestLayers() -> CzOutcome {
    CzOutcome.holding((taken() as Manifest).layers)
  }

  func manifestAnnotations() -> CzOutcome {
    CzOutcome.holding((taken() as Manifest).annotations)
  }

  func manifestSubject() -> CzOutcome {
    CzOutcome.holding((taken() as Manifest).subject)
  }

  func manifestArtifactType() -> String? {
    (taken() as Manifest).artifactType
  }

  // `ImageConfig`.

  func imageConfigUser() -> String? {
    (taken() as ImageConfig).user
  }

  func imageConfigEnv() -> CzOutcome {
    CzOutcome.holding((taken() as ImageConfig).env)
  }

  func imageConfigEntrypoint() -> CzOutcome {
    CzOutcome.holding((taken() as ImageConfig).entrypoint)
  }

  func imageConfigCmd() -> CzOutcome {
    CzOutcome.holding((taken() as ImageConfig).cmd)
  }

  func imageConfigWorkingDir() -> String? {
    (taken() as ImageConfig).workingDir
  }

  func imageConfigLabels() -> CzOutcome {
    CzOutcome.holding((taken() as ImageConfig).labels)
  }

  func imageConfigStopSignal() -> String? {
    (taken() as ImageConfig).stopSignal
  }

  // `Rootfs`.

  func rootfsType() -> String {
    (taken() as Rootfs).type
  }

  func rootfsDiffIDs() -> RustVec<RustString> {
    rustStrings((taken() as Rootfs).diffIDs)
  }

  // `History`.

  func historyCreated() -> String? {
    (taken() as History).created
  }

  func historyCreatedBy() -> String? {
    (taken() as History).createdBy
  }

  func historyAuthor() -> String? {
    (taken() as History).author
  }

  func historyComment() -> String? {
    (taken() as History).comment
  }

  func historyEmptyLayer() -> Bool? {
    (taken() as History).emptyLayer
  }

  // `ContainerizationOCI.Image`.

  func ociImageCreated() -> String? {
    (taken() as ContainerizationOCI.Image).created
  }

  func ociImageAuthor() -> String? {
    (taken() as ContainerizationOCI.Image).author
  }

  func ociImageArchitecture() -> String {
    (taken() as ContainerizationOCI.Image).architecture
  }

  func ociImageOs() -> String {
    (taken() as ContainerizationOCI.Image).os
  }

  func ociImageOsVersion() -> String? {
    (taken() as ContainerizationOCI.Image).osVersion
  }

  func ociImageOsFeatures() -> CzOutcome {
    CzOutcome.holding((taken() as ContainerizationOCI.Image).osFeatures)
  }

  func ociImageVariant() -> String? {
    (taken() as ContainerizationOCI.Image).variant
  }

  func ociImageConfig() -> CzOutcome {
    CzOutcome.holding((taken() as ContainerizationOCI.Image).config)
  }

  func ociImageRootfs() -> CzOutcome {
    CzOutcome.holding((taken() as ContainerizationOCI.Image).rootfs)
  }

  func ociImageHistory() -> CzOutcome {
    CzOutcome.holding((taken() as ContainerizationOCI.Image).history)
  }
}

/// Swift's `MediaTypes`, in the order of Rust's `MediaTypes::ALL`, for a test
/// that compares Rust's copies with them.
func mediaTypes() -> RustVec<RustString> {
  rustStrings([
    MediaTypes.descriptor,
    MediaTypes.layoutHeader,
    MediaTypes.index,
    MediaTypes.imageManifest,
    MediaTypes.imageConfig,
    MediaTypes.emptyJSON,
    MediaTypes.dockerManifest,
    MediaTypes.dockerManifestList,
    MediaTypes.dockerImageConfig,
    MediaTypes.imageLayer,
    MediaTypes.imageLayerGzip,
    MediaTypes.imageLayerZstd,
    MediaTypes.dockerImageLayer,
    MediaTypes.dockerImageLayerGzip,
    MediaTypes.dockerImageLayerZstd,
    MediaTypes.inTotoAttestationBlob,
  ])
}

/// Swift's `AnnotationKeys`, in the order of Rust's `AnnotationKeys::ALL`.
func annotationKeys() -> RustVec<RustString> {
  rustStrings([
    AnnotationKeys.containerizationIndexIndirect,
    AnnotationKeys.containerizationImageName,
    AnnotationKeys.containerdImageName,
    AnnotationKeys.openContainersImageName,
  ])
}

func currentPlatform() -> CzOutcome {
  CzOutcome { Platform.current }
}

func parsePlatform(platform: RustStr) -> CzOutcome {
  let platform = platform.toString()

  return CzOutcome { try Platform(from: platform) }
}

func platformDescription(platform: RustPlatform) -> CzOutcome {
  let platform = Platform(platform)

  return CzOutcome { platform.description }
}

func platformEquals(lhs: RustPlatform, rhs: RustPlatform) -> CzOutcome {
  let lhs = Platform(lhs)
  let rhs = Platform(rhs)

  return CzOutcome { lhs == rhs }
}

func platformMatches(lhs: RustPlatform, rhs: RustPlatform) -> CzOutcome {
  let lhs = Platform(lhs)
  let rhs = Platform(rhs)

  return CzOutcome { lhs ~= rhs }
}