feat: Add bootc support (#448)

Adds support for using `bootc` as the preferred method for booting from
a locally created image. This new method gets rid of the need to create
a tarball and move it to the correct place and instead it will make use
of `podman scp` which copies the image to the root `containers-storage`
and then has `rpm-ostree` and `bootc` boot from that store.

Closes #418 
Closes #200
This commit is contained in:
Gerald Pinder 2025-08-09 14:05:59 -04:00 committed by GitHub
parent 2c525854c9
commit 3a0be4099a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 2991 additions and 1857 deletions

View file

@ -10,7 +10,7 @@ use std::{
borrow::Borrow,
fmt::Debug,
process::{ExitStatus, Output},
sync::{Mutex, RwLock},
sync::{LazyLock, RwLock, atomic::AtomicBool},
time::Duration,
};
@ -24,12 +24,13 @@ use log::{info, trace, warn};
use miette::{Result, miette};
use oci_distribution::Reference;
use opts::{
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, CreateContainerOpts, GenerateImageNameOpts,
GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RemoveContainerOpts,
RemoveImageOpts, RunOpts, SignOpts, TagOpts, VerifyOpts,
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CopyOciDirOpts,
CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts,
GetMetadataOpts, PruneOpts, PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts,
RunOpts, SignOpts, SwitchOpts, TagOpts, VerifyOpts, VolumeOpts,
};
use types::{
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, Platform,
BootDriverType, BuildDriverType, CiDriverType, ImageMetadata, InspectDriverType, Platform,
RunDriverType, SigningDriverType,
};
use uuid::Uuid;
@ -39,10 +40,15 @@ use crate::logging::Logger;
pub use self::{
buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver,
github_driver::GithubDriver, gitlab_driver::GitlabDriver, local_driver::LocalDriver,
podman_driver::PodmanDriver, sigstore_driver::SigstoreDriver, skopeo_driver::SkopeoDriver,
traits::*,
podman_driver::PodmanDriver, rpm_ostree_driver::RpmOstreeDriver,
sigstore_driver::SigstoreDriver, skopeo_driver::SkopeoDriver, traits::*,
};
#[cfg(feature = "bootc")]
pub use bootc_driver::BootcDriver;
#[cfg(feature = "bootc")]
mod bootc_driver;
mod buildah_driver;
mod cosign_driver;
mod docker_driver;
@ -52,22 +58,25 @@ mod gitlab_driver;
mod local_driver;
pub mod opts;
mod podman_driver;
mod rpm_ostree_driver;
mod sigstore_driver;
mod skopeo_driver;
mod traits;
pub mod types;
static INIT: std::sync::LazyLock<Mutex<bool>> = std::sync::LazyLock::new(|| Mutex::new(false));
static SELECTED_BUILD_DRIVER: std::sync::LazyLock<RwLock<Option<BuildDriverType>>> =
std::sync::LazyLock::new(|| RwLock::new(None));
static SELECTED_INSPECT_DRIVER: std::sync::LazyLock<RwLock<Option<InspectDriverType>>> =
std::sync::LazyLock::new(|| RwLock::new(None));
static SELECTED_RUN_DRIVER: std::sync::LazyLock<RwLock<Option<RunDriverType>>> =
std::sync::LazyLock::new(|| RwLock::new(None));
static SELECTED_SIGNING_DRIVER: std::sync::LazyLock<RwLock<Option<SigningDriverType>>> =
std::sync::LazyLock::new(|| RwLock::new(None));
static SELECTED_CI_DRIVER: std::sync::LazyLock<RwLock<Option<CiDriverType>>> =
std::sync::LazyLock::new(|| RwLock::new(None));
static INIT: AtomicBool = AtomicBool::new(false);
static SELECTED_BUILD_DRIVER: LazyLock<RwLock<Option<BuildDriverType>>> =
LazyLock::new(|| RwLock::new(None));
static SELECTED_INSPECT_DRIVER: LazyLock<RwLock<Option<InspectDriverType>>> =
LazyLock::new(|| RwLock::new(None));
static SELECTED_RUN_DRIVER: LazyLock<RwLock<Option<RunDriverType>>> =
LazyLock::new(|| RwLock::new(None));
static SELECTED_SIGNING_DRIVER: LazyLock<RwLock<Option<SigningDriverType>>> =
LazyLock::new(|| RwLock::new(None));
static SELECTED_CI_DRIVER: LazyLock<RwLock<Option<CiDriverType>>> =
LazyLock::new(|| RwLock::new(None));
static SELECTED_BOOT_DRIVER: LazyLock<RwLock<Option<BootDriverType>>> =
LazyLock::new(|| RwLock::new(None));
/// Args for selecting the various drivers to use for runtime.
///
@ -95,6 +104,9 @@ pub struct DriverArgs {
/// containers.
#[arg(short = 'R', long)]
run_driver: Option<RunDriverType>,
#[arg(short = 'T', long)]
boot_driver: Option<BootDriverType>,
}
macro_rules! impl_driver_type {
@ -108,12 +120,13 @@ macro_rules! impl_driver_init {
(@) => { };
($init:ident; $($tail:tt)*) => {
{
let mut initialized = $init.lock().expect("Must lock INIT");
if !*initialized {
if $init.compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire
).is_ok() {
impl_driver_init!(@ $($tail)*);
*initialized = true;
}
}
};
@ -162,6 +175,7 @@ impl Driver {
args.inspect_driver => SELECTED_INSPECT_DRIVER;
args.run_driver => SELECTED_RUN_DRIVER;
args.signing_driver => SELECTED_SIGNING_DRIVER;
args.boot_driver => SELECTED_BOOT_DRIVER;
default => SELECTED_CI_DRIVER;
}
}
@ -206,7 +220,7 @@ impl Driver {
info!("Retrieving OS version from {oci_ref}");
let os_version = Self::get_metadata(
&GetMetadataOpts::builder()
GetMetadataOpts::builder()
.image(oci_ref)
.maybe_platform(platform)
.build(),
@ -247,6 +261,10 @@ impl Driver {
pub fn get_ci_driver() -> CiDriverType {
impl_driver_type!(SELECTED_CI_DRIVER)
}
pub fn get_boot_driver() -> BootDriverType {
impl_driver_type!(SELECTED_BOOT_DRIVER)
}
}
#[cached(
@ -278,9 +296,9 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
};
let output = Driver::run_output(
&RunOpts::builder()
.image(oci_ref.to_string())
.args(bon::vec![
RunOpts::builder()
.image(&oci_ref.to_string())
.args(&bon::vec![
"/bin/bash",
"-c",
r#"awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release"#,
@ -291,7 +309,7 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
)?;
if should_remove {
Driver::remove_image(&RemoveImageOpts::builder().image(oci_ref).build())?;
Driver::remove_image(RemoveImageOpts::builder().image(oci_ref).build())?;
}
progress.finish_and_clear();
@ -314,15 +332,15 @@ macro_rules! impl_build_driver {
}
impl BuildDriver for Driver {
fn build(opts: &BuildOpts) -> Result<()> {
fn build(opts: BuildOpts) -> Result<()> {
impl_build_driver!(build(opts))
}
fn tag(opts: &TagOpts) -> Result<()> {
fn tag(opts: TagOpts) -> Result<()> {
impl_build_driver!(tag(opts))
}
fn push(opts: &PushOpts) -> Result<()> {
fn push(opts: PushOpts) -> Result<()> {
impl_build_driver!(push(opts))
}
@ -330,11 +348,11 @@ impl BuildDriver for Driver {
impl_build_driver!(login())
}
fn prune(opts: &opts::PruneOpts) -> Result<()> {
fn prune(opts: PruneOpts) -> Result<()> {
impl_build_driver!(prune(opts))
}
fn build_tag_push(opts: &BuildTagPushOpts) -> Result<Vec<String>> {
fn build_tag_push(opts: BuildTagPushOpts) -> Result<Vec<String>> {
impl_build_driver!(build_tag_push(opts))
}
}
@ -349,19 +367,19 @@ macro_rules! impl_signing_driver {
}
impl SigningDriver for Driver {
fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> {
fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()> {
impl_signing_driver!(generate_key_pair(opts))
}
fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> {
fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()> {
impl_signing_driver!(check_signing_files(opts))
}
fn sign(opts: &SignOpts) -> Result<()> {
fn sign(opts: SignOpts) -> Result<()> {
impl_signing_driver!(sign(opts))
}
fn verify(opts: &VerifyOpts) -> Result<()> {
fn verify(opts: VerifyOpts) -> Result<()> {
impl_signing_driver!(verify(opts))
}
@ -381,7 +399,7 @@ macro_rules! impl_inspect_driver {
}
impl InspectDriver for Driver {
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
impl_inspect_driver!(get_metadata(opts))
}
}
@ -396,23 +414,23 @@ macro_rules! impl_run_driver {
}
impl RunDriver for Driver {
fn run(opts: &RunOpts) -> Result<ExitStatus> {
fn run(opts: RunOpts) -> Result<ExitStatus> {
impl_run_driver!(run(opts))
}
fn run_output(opts: &RunOpts) -> Result<Output> {
fn run_output(opts: RunOpts) -> Result<Output> {
impl_run_driver!(run_output(opts))
}
fn create_container(opts: &CreateContainerOpts) -> Result<types::ContainerId> {
fn create_container(opts: CreateContainerOpts) -> Result<types::ContainerId> {
impl_run_driver!(create_container(opts))
}
fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
fn remove_container(opts: RemoveContainerOpts) -> Result<()> {
impl_run_driver!(remove_container(opts))
}
fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
fn remove_image(opts: RemoveImageOpts) -> Result<()> {
impl_run_driver!(remove_image(opts))
}
@ -444,7 +462,7 @@ impl CiDriver for Driver {
impl_ci_driver!(oidc_provider())
}
fn generate_tags(opts: &GenerateTagsOpts) -> Result<Vec<String>> {
fn generate_tags(opts: GenerateTagsOpts) -> Result<Vec<String>> {
impl_ci_driver!(generate_tags(opts))
}
@ -469,27 +487,52 @@ impl CiDriver for Driver {
}
impl ContainerMountDriver for Driver {
fn mount_container(opts: &opts::ContainerOpts) -> Result<types::MountId> {
fn mount_container(opts: ContainerOpts) -> Result<types::MountId> {
PodmanDriver::mount_container(opts)
}
fn unmount_container(opts: &opts::ContainerOpts) -> Result<()> {
fn unmount_container(opts: ContainerOpts) -> Result<()> {
PodmanDriver::unmount_container(opts)
}
fn remove_volume(opts: &opts::VolumeOpts) -> Result<()> {
fn remove_volume(opts: VolumeOpts) -> Result<()> {
PodmanDriver::remove_volume(opts)
}
}
impl OciCopy for Driver {
fn copy_oci_dir(opts: &opts::CopyOciDirOpts) -> Result<()> {
fn copy_oci_dir(opts: CopyOciDirOpts) -> Result<()> {
SkopeoDriver::copy_oci_dir(opts)
}
}
impl RechunkDriver for Driver {
fn rechunk(opts: &opts::RechunkOpts) -> Result<Vec<String>> {
fn rechunk(opts: RechunkOpts) -> Result<Vec<String>> {
PodmanDriver::rechunk(opts)
}
}
macro_rules! impl_boot_driver {
($func:ident($($args:expr),*)) => {
match Self::get_boot_driver() {
#[cfg(feature = "bootc")]
BootDriverType::Bootc => BootcDriver::$func($($args,)*),
BootDriverType::RpmOstree => RpmOstreeDriver::$func($($args,)*),
BootDriverType::None => ::miette::bail!("Cannot perform boot operation when no boot driver exists."),
}
};
}
impl BootDriver for Driver {
fn status() -> Result<Box<dyn BootStatus>> {
impl_boot_driver!(status())
}
fn switch(opts: SwitchOpts) -> Result<()> {
impl_boot_driver!(switch(opts))
}
fn upgrade(opts: SwitchOpts) -> Result<()> {
impl_boot_driver!(upgrade(opts))
}
}