containerization-framework 0.3.0

Rust bindings for Apple's Containerization framework: Linux containers in lightweight VMs, in-process and without a daemon.
//! `ContainerManager`, and the options for its `create`s: [`CreateOptions`]
//! and [`RootfsCreateOptions`].

use super::GIB;
use super::Image;
use super::ImageStore;
use super::Kernel;
use super::LinuxContainer;
use super::Mount;
use super::VmResources;
use super::linux_container;
use crate::error::Error;
use crate::platform;
use crate::platform::ffi;

/// `ContainerManager.create(_:image:...)`'s defaulted arguments, between the
/// image and the configuration. [`Default`] is Swift's defaults.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CreateOptions {
  pub rootfs_size_in_bytes: u64,
  pub writable_layer_size_in_bytes: Option<u64>,
  pub read_only: bool,
  pub networking: bool,
  pub vm: VmResources,
}

impl Default for CreateOptions {
  fn default() -> Self {
    Self {
      rootfs_size_in_bytes: 8 * GIB,
      writable_layer_size_in_bytes: None,
      read_only: false,
      networking: true,
      vm: VmResources::default(),
    }
  }
}

/// `ContainerManager.create(_:image:rootfs:...)`'s defaulted arguments, between
/// the rootfs and the configuration. [`Default`] is Swift's defaults.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RootfsCreateOptions {
  pub writable_layer: Option<Mount>,
  pub networking: bool,
  pub vm: VmResources,
}

impl Default for RootfsCreateOptions {
  fn default() -> Self {
    Self {
      writable_layer: None,
      networking: true,
      vm: VmResources::default(),
    }
  }
}

/// `ContainerManager`, with no `Network`.
pub struct ContainerManager {
  handle: ffi::CzContainerManager,
}

// Swift's `ContainerManager` is `Sendable`; `create` and `delete` take
// `&mut self` as Swift's are `mutating`.
unsafe impl Send for ContainerManager {}
unsafe impl Sync for ContainerManager {}

impl ContainerManager {
  /// `ContainerManager(kernel:initfs:imageStore:rosetta:nestedVirtualization:)`.
  pub fn new(
    kernel: &Kernel,
    initfs: &Mount,
    image_store: &ImageStore,
    rosetta: bool,
    nested_virtualization: bool,
  ) -> Result<Self, Error> {
    let outcome = platform::outcome(
      image_store
        .handle
        .container_manager(kernel.clone(), initfs.clone(), rosetta, nested_virtualization),
      "make a container manager",
    )?;

    Ok(Self {
      handle: outcome.container_manager(),
    })
  }

  /// `ContainerManager(kernel:initfsReference:imageStore:rosetta:nestedVirtualization:)`.
  pub fn with_initfs_reference(
    kernel: &Kernel,
    initfs_reference: &str,
    image_store: &ImageStore,
    rosetta: bool,
    nested_virtualization: bool,
  ) -> Result<Self, Error> {
    let outcome = platform::outcome(
      image_store.handle.container_manager_with_initfs_reference(
        kernel.clone(),
        initfs_reference,
        rosetta,
        nested_virtualization,
      ),
      format!("make a container manager booting {initfs_reference}"),
    )?;

    Ok(Self {
      handle: outcome.container_manager(),
    })
  }

  /// `ContainerManager.create(_:image:rootfsSizeInBytes:writableLayerSizeInBytes:readOnly:networking:vm:configuration:)`.
  ///
  /// `configuration` runs on another thread, as Swift's closure does, against
  /// the configuration the manager seeded.
  pub fn create(
    &mut self,
    id: &str,
    image: &Image,
    options: CreateOptions,
    configuration: impl FnOnce(&mut linux_container::Configuration) + Send + 'static,
  ) -> Result<LinuxContainer, Error> {
    let outcome = self.handle.create(
      id,
      image.handle.duplicate(),
      options,
      linux_container::Configuration::default(),
      platform::Configure::new(configuration),
    );

    platform::outcome(outcome, format!("create {id}")).map(|outcome| LinuxContainer {
      handle: outcome.linux_container(),
    })
  }

  /// `ContainerManager.create(_:image:rootfs:writableLayer:networking:vm:configuration:)`.
  ///
  /// `configuration` runs on another thread, as Swift's closure does, against
  /// the configuration the manager seeded. Swift seeds a boot log in the
  /// manager's directory for `id`, but only the image `create` makes that
  /// directory, so set `boot_log` to somewhere that exists.
  pub fn create_with_rootfs(
    &mut self,
    id: &str,
    image: &Image,
    rootfs: Mount,
    options: RootfsCreateOptions,
    configuration: impl FnOnce(&mut linux_container::Configuration) + Send + 'static,
  ) -> Result<LinuxContainer, Error> {
    let outcome = self.handle.create_with_rootfs(
      id,
      image.handle.duplicate(),
      rootfs,
      options,
      linux_container::Configuration::default(),
      platform::Configure::new(configuration),
    );

    platform::outcome(outcome, format!("create {id}")).map(|outcome| LinuxContainer {
      handle: outcome.linux_container(),
    })
  }

  /// `ContainerManager.delete(_:)`.
  pub fn delete(&mut self, id: &str) -> Result<(), Error> {
    platform::outcome(self.handle.delete(id), format!("delete {id}")).map(|_| ())
  }
}