mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 15:45:53 +01:00
Gate composefs-native behind composefs-backend flag
Refactor to use `#[cfg(feature = "composefs-backend")]` to gate composefs native features behind the flag. Gate the following features - `--composefs-native` and its corresponding cli args - Installing/Switching/Upgrading/RollingBack of composefs native system - Create separate install, rollback functions for ostree for a bit cleaner of a setup Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
use std::io::Read;
|
||||
use std::{io::Read, sync::OnceLock};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use bootc_kernel_cmdline::Cmdline;
|
||||
use fn_error_context::context;
|
||||
|
||||
use crate::{
|
||||
bootc_composefs::boot::BootType,
|
||||
composefs_consts::{BOOT_LOADER_ENTRIES, USER_CFG},
|
||||
composefs_consts::{BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, USER_CFG},
|
||||
parsers::{
|
||||
bls_config::{parse_bls_config, BLSConfig},
|
||||
grub_menuconfig::{parse_grub_menuentry_file, MenuEntry},
|
||||
},
|
||||
spec::{BootEntry, BootOrder, Host, HostSpec, ImageReference, ImageStatus},
|
||||
status::composefs_booted,
|
||||
};
|
||||
|
||||
use std::str::FromStr;
|
||||
@@ -35,6 +35,49 @@ use crate::composefs_consts::{
|
||||
use crate::install::EFIVARFS;
|
||||
use crate::spec::Bootloader;
|
||||
|
||||
/// A parsed composefs command line
|
||||
pub(crate) struct ComposefsCmdline {
|
||||
#[allow(dead_code)]
|
||||
pub insecure: bool,
|
||||
pub digest: Box<str>,
|
||||
}
|
||||
|
||||
impl ComposefsCmdline {
|
||||
pub(crate) fn new(s: &str) -> Self {
|
||||
let (insecure, digest_str) = s
|
||||
.strip_prefix('?')
|
||||
.map(|v| (true, v))
|
||||
.unwrap_or_else(|| (false, s));
|
||||
ComposefsCmdline {
|
||||
insecure,
|
||||
digest: digest_str.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ComposefsCmdline {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let insecure = if self.insecure { "?" } else { "" };
|
||||
write!(f, "{}={}{}", COMPOSEFS_CMDLINE, insecure, self.digest)
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if we have composefs=<digest> in /proc/cmdline
|
||||
pub(crate) fn composefs_booted() -> Result<Option<&'static ComposefsCmdline>> {
|
||||
static CACHED_DIGEST_VALUE: OnceLock<Option<ComposefsCmdline>> = OnceLock::new();
|
||||
if let Some(v) = CACHED_DIGEST_VALUE.get() {
|
||||
return Ok(v.as_ref());
|
||||
}
|
||||
let cmdline = Cmdline::from_proc()?;
|
||||
let Some(kv) = cmdline.find_str(COMPOSEFS_CMDLINE) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(v) = kv.value else { return Ok(None) };
|
||||
let v = ComposefsCmdline::new(v);
|
||||
let r = CACHED_DIGEST_VALUE.get_or_init(|| Some(v));
|
||||
Ok(r.as_ref())
|
||||
}
|
||||
|
||||
// Need str to store lifetime
|
||||
pub(crate) fn get_sorted_uki_boot_entries<'a>(
|
||||
boot_dir: &Dir,
|
||||
@@ -336,6 +379,17 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_composefs_parsing() {
|
||||
const DIGEST: &str = "8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52";
|
||||
let v = ComposefsCmdline::new(DIGEST);
|
||||
assert!(!v.insecure);
|
||||
assert_eq!(v.digest.as_ref(), DIGEST);
|
||||
let v = ComposefsCmdline::new(&format!("?{}", DIGEST));
|
||||
assert!(v.insecure);
|
||||
assert_eq!(v.digest.as_ref(), DIGEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_bls_boot_entries() -> Result<()> {
|
||||
let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;
|
||||
|
||||
@@ -28,15 +28,16 @@ use ostree_ext::sysroot::SysrootLock;
|
||||
use schemars::schema_for;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::bootc_composefs::rollback::composefs_rollback;
|
||||
use crate::bootc_composefs::switch::switch_composefs;
|
||||
use crate::bootc_composefs::update::upgrade_composefs;
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
use crate::bootc_composefs::{
|
||||
rollback::composefs_rollback, status::composefs_booted, switch::switch_composefs,
|
||||
update::upgrade_composefs,
|
||||
};
|
||||
use crate::deploy::RequiredHostSpec;
|
||||
use crate::lints;
|
||||
use crate::progress_jsonl::{ProgressWriter, RawProgressFd};
|
||||
use crate::spec::Host;
|
||||
use crate::spec::ImageReference;
|
||||
use crate::status::composefs_booted;
|
||||
use crate::utils::sigpolicy_from_opt;
|
||||
|
||||
/// Shared progress options
|
||||
@@ -1128,29 +1129,21 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
|
||||
|
||||
/// Implementation of the `bootc rollback` CLI command.
|
||||
#[context("Rollback")]
|
||||
async fn rollback(opts: RollbackOpts) -> Result<()> {
|
||||
if composefs_booted()?.is_some() {
|
||||
composefs_rollback().await?
|
||||
} else {
|
||||
let sysroot = &get_storage().await?;
|
||||
let ostree = sysroot.get_ostree()?;
|
||||
crate::deploy::rollback(sysroot).await?;
|
||||
async fn rollback(opts: &RollbackOpts) -> Result<()> {
|
||||
let sysroot = &get_storage().await?;
|
||||
let ostree = sysroot.get_ostree()?;
|
||||
crate::deploy::rollback(sysroot).await?;
|
||||
|
||||
if opts.soft_reboot.is_some() {
|
||||
// Get status of rollback deployment to check soft-reboot capability
|
||||
let host = crate::status::get_status_require_booted(ostree)?.2;
|
||||
if opts.soft_reboot.is_some() {
|
||||
// Get status of rollback deployment to check soft-reboot capability
|
||||
let host = crate::status::get_status_require_booted(ostree)?.2;
|
||||
|
||||
handle_soft_reboot(
|
||||
opts.soft_reboot,
|
||||
host.status.rollback.as_ref(),
|
||||
"rollback",
|
||||
|| soft_reboot_rollback(ostree),
|
||||
)?;
|
||||
}
|
||||
};
|
||||
|
||||
if opts.apply {
|
||||
crate::reboot::reboot()?;
|
||||
handle_soft_reboot(
|
||||
opts.soft_reboot,
|
||||
host.status.rollback.as_ref(),
|
||||
"rollback",
|
||||
|| soft_reboot_rollback(ostree),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1298,20 +1291,44 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
|
||||
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
|
||||
match opt {
|
||||
Opt::Upgrade(opts) => {
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
if composefs_booted()?.is_some() {
|
||||
upgrade_composefs(opts).await
|
||||
} else {
|
||||
upgrade(opts).await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
upgrade(opts).await
|
||||
}
|
||||
Opt::Switch(opts) => {
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
if composefs_booted()?.is_some() {
|
||||
switch_composefs(opts).await
|
||||
} else {
|
||||
switch(opts).await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
switch(opts).await
|
||||
}
|
||||
Opt::Rollback(opts) => {
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
if composefs_booted()?.is_some() {
|
||||
composefs_rollback().await?
|
||||
} else {
|
||||
rollback(&opts).await?
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
rollback(&opts).await?;
|
||||
|
||||
if opts.apply {
|
||||
crate::reboot::reboot()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Opt::Rollback(opts) => rollback(opts).await,
|
||||
Opt::Edit(opts) => edit(opts).await,
|
||||
Opt::UsrOverlay => usroverlay().await,
|
||||
Opt::Container(opts) => match opts {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
/// composefs= paramter in kernel cmdline
|
||||
pub const COMPOSEFS_CMDLINE: &str = "composefs";
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ use cap_std_ext::prelude::CapStdExtDirExt;
|
||||
use clap::ValueEnum;
|
||||
use fn_error_context::context;
|
||||
use ostree::gio;
|
||||
use ostree_ext::composefs::fsverity::FsVerityHashValue;
|
||||
use ostree_ext::ostree;
|
||||
use ostree_ext::ostree_prepareroot::{ComposefsState, Tristate};
|
||||
use ostree_ext::prelude::Cast;
|
||||
@@ -54,8 +53,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "install-to-disk")]
|
||||
use self::baseline::InstallBlockDeviceOpts;
|
||||
use crate::bootc_composefs::boot::setup_composefs_boot;
|
||||
use crate::bootc_composefs::repo::initialize_composefs_repository;
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
use crate::bootc_composefs::{boot::setup_composefs_boot, repo::initialize_composefs_repository};
|
||||
use crate::boundimage::{BoundImage, ResolvedBoundImage};
|
||||
use crate::containerenv::ContainerExecutionInfo;
|
||||
use crate::deploy::{prepare_for_pull, pull_from_prepared, PreparedImportMeta, PreparedPullResult};
|
||||
@@ -67,6 +66,8 @@ use crate::task::Task;
|
||||
use crate::utils::sigpolicy_from_opt;
|
||||
use bootc_kernel_cmdline::Cmdline;
|
||||
use bootc_mount::Filesystem;
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
use composefs::fsverity::FsVerityHashValue;
|
||||
|
||||
/// The toplevel boot directory
|
||||
const BOOT: &str = "boot";
|
||||
@@ -263,10 +264,12 @@ pub(crate) struct InstallToDiskOpts {
|
||||
|
||||
#[clap(long)]
|
||||
#[serde(default)]
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub(crate) composefs_native: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
#[serde(flatten)]
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub(crate) composefs_opts: InstallComposefsOpts,
|
||||
}
|
||||
|
||||
@@ -410,6 +413,7 @@ pub(crate) struct State {
|
||||
pub(crate) tempdir: TempDir,
|
||||
|
||||
// If Some, then --composefs_native is passed
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub(crate) composefs_options: Option<InstallComposefsOpts>,
|
||||
}
|
||||
|
||||
@@ -537,7 +541,7 @@ impl FromStr for MountSpec {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "install-to-disk")]
|
||||
#[cfg(all(feature = "install-to-disk", feature = "composefs-backend"))]
|
||||
impl InstallToDiskOpts {
|
||||
pub(crate) fn validate(&self) -> Result<()> {
|
||||
if !self.composefs_native {
|
||||
@@ -1196,7 +1200,7 @@ async fn prepare_install(
|
||||
config_opts: InstallConfigOpts,
|
||||
source_opts: InstallSourceOpts,
|
||||
target_opts: InstallTargetOpts,
|
||||
composefs_opts: Option<InstallComposefsOpts>,
|
||||
_composefs_opts: Option<InstallComposefsOpts>,
|
||||
) -> Result<Arc<State>> {
|
||||
tracing::trace!("Preparing install");
|
||||
let rootfs = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())
|
||||
@@ -1341,7 +1345,8 @@ async fn prepare_install(
|
||||
container_root: rootfs,
|
||||
tempdir,
|
||||
host_is_container,
|
||||
composefs_options: composefs_opts,
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
composefs_options: _composefs_opts,
|
||||
});
|
||||
|
||||
Ok(state)
|
||||
@@ -1440,6 +1445,48 @@ impl BoundImages {
|
||||
}
|
||||
}
|
||||
|
||||
async fn ostree_install(state: &State, rootfs: &RootSetup, cleanup: Cleanup) -> Result<()> {
|
||||
// We verify this upfront because it's currently required by bootupd
|
||||
let boot_uuid = rootfs
|
||||
.get_boot_uuid()?
|
||||
.or(rootfs.rootfs_uuid.as_deref())
|
||||
.ok_or_else(|| anyhow!("No uuid for boot/root"))?;
|
||||
tracing::debug!("boot uuid={boot_uuid}");
|
||||
|
||||
let bound_images = BoundImages::from_state(state).await?;
|
||||
|
||||
// Initialize the ostree sysroot (repo, stateroot, etc.)
|
||||
|
||||
{
|
||||
let (sysroot, has_ostree) = initialize_ostree_root(state, rootfs).await?;
|
||||
|
||||
install_with_sysroot(
|
||||
state,
|
||||
rootfs,
|
||||
&sysroot,
|
||||
&boot_uuid,
|
||||
bound_images,
|
||||
has_ostree,
|
||||
)
|
||||
.await?;
|
||||
let ostree = sysroot.get_ostree()?;
|
||||
|
||||
if matches!(cleanup, Cleanup::TriggerOnNextBoot) {
|
||||
let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
|
||||
tracing::debug!("Writing {DESTRUCTIVE_CLEANUP}");
|
||||
sysroot_dir.atomic_write(format!("etc/{}", DESTRUCTIVE_CLEANUP), b"")?;
|
||||
}
|
||||
|
||||
// We must drop the sysroot here in order to close any open file
|
||||
// descriptors.
|
||||
};
|
||||
|
||||
// Run this on every install as the penultimate step
|
||||
install_finalize(&rootfs.physical_root_path).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn install_to_filesystem_impl(
|
||||
state: &State,
|
||||
rootfs: &mut RootSetup,
|
||||
@@ -1463,57 +1510,20 @@ async fn install_to_filesystem_impl(
|
||||
}
|
||||
}
|
||||
|
||||
// We verify this upfront because it's currently required by bootupd
|
||||
let boot_uuid = rootfs
|
||||
.get_boot_uuid()?
|
||||
.or(rootfs.rootfs_uuid.as_deref())
|
||||
.ok_or_else(|| anyhow!("No uuid for boot/root"))?;
|
||||
tracing::debug!("boot uuid={boot_uuid}");
|
||||
|
||||
let bound_images = BoundImages::from_state(state).await?;
|
||||
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
if state.composefs_options.is_some() {
|
||||
// Load a fd for the mounted target physical root
|
||||
|
||||
let (id, verity) = initialize_composefs_repository(state, rootfs).await?;
|
||||
|
||||
tracing::info!(
|
||||
"id = {id}, verity = {verity}",
|
||||
id = hex::encode(id),
|
||||
verity = verity.to_hex()
|
||||
);
|
||||
|
||||
tracing::info!("id: {}, verity: {}", hex::encode(id), verity.to_hex());
|
||||
setup_composefs_boot(rootfs, state, &hex::encode(id))?;
|
||||
} else {
|
||||
// Initialize the ostree sysroot (repo, stateroot, etc.)
|
||||
|
||||
{
|
||||
let (sysroot, has_ostree) = initialize_ostree_root(state, rootfs).await?;
|
||||
|
||||
install_with_sysroot(
|
||||
state,
|
||||
rootfs,
|
||||
&sysroot,
|
||||
&boot_uuid,
|
||||
bound_images,
|
||||
has_ostree,
|
||||
)
|
||||
.await?;
|
||||
let ostree = sysroot.get_ostree()?;
|
||||
|
||||
if matches!(cleanup, Cleanup::TriggerOnNextBoot) {
|
||||
let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
|
||||
tracing::debug!("Writing {DESTRUCTIVE_CLEANUP}");
|
||||
sysroot_dir.atomic_write(format!("etc/{}", DESTRUCTIVE_CLEANUP), b"")?;
|
||||
}
|
||||
|
||||
// We must drop the sysroot here in order to close any open file
|
||||
// descriptors.
|
||||
};
|
||||
|
||||
// Run this on every install as the penultimate step
|
||||
install_finalize(&rootfs.physical_root_path).await?;
|
||||
ostree_install(state, rootfs, cleanup).await?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
ostree_install(state, rootfs, cleanup).await?;
|
||||
|
||||
// Finalize mounted filesystems
|
||||
if !rootfs.skip_finalize {
|
||||
let bootfs = rootfs.boot.as_ref().map(|_| ("boot", "boot"));
|
||||
@@ -1533,6 +1543,7 @@ fn installation_complete() {
|
||||
#[context("Installing to disk")]
|
||||
#[cfg(feature = "install-to-disk")]
|
||||
pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
opts.validate()?;
|
||||
|
||||
// Log the disk installation operation to systemd journal
|
||||
@@ -1576,15 +1587,22 @@ pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
|
||||
} else if !target_blockdev_meta.file_type().is_block_device() {
|
||||
anyhow::bail!("Not a block device: {}", block_opts.device);
|
||||
}
|
||||
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
let composefs_arg = if opts.composefs_native {
|
||||
Some(opts.composefs_opts)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
let composefs_arg = None;
|
||||
|
||||
let state = prepare_install(
|
||||
opts.config_opts,
|
||||
opts.source_opts,
|
||||
opts.target_opts,
|
||||
if opts.composefs_native {
|
||||
Some(opts.composefs_opts)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
composefs_arg,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! to provide a fully "container native" tool for using
|
||||
//! bootable container images.
|
||||
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
mod bootc_composefs;
|
||||
pub(crate) mod bootc_kargs;
|
||||
mod bootloader;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! This module parses the config files for the spec.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Parser for GRUB menuentry configuration files using nom combinators.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use nom::{
|
||||
|
||||
@@ -11,6 +11,7 @@ use ostree_ext::{container::OstreeImageReference, oci_spec};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
use crate::bootc_composefs::boot::BootType;
|
||||
use crate::{k8sapitypes, status::Slot};
|
||||
|
||||
@@ -198,6 +199,7 @@ impl FromStr for Bootloader {
|
||||
/// A bootable entry
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub struct BootEntryComposefs {
|
||||
/// The erofs verity
|
||||
pub verity: String,
|
||||
@@ -228,6 +230,7 @@ pub struct BootEntry {
|
||||
/// If this boot entry is ostree based, the corresponding state
|
||||
pub ostree: Option<BootEntryOstree>,
|
||||
/// If this boot entry is composefs based, the corresponding state
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub composefs: Option<BootEntryComposefs>,
|
||||
}
|
||||
|
||||
@@ -300,6 +303,7 @@ impl Host {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub(crate) fn require_composefs_booted(&self) -> anyhow::Result<&BootEntryComposefs> {
|
||||
let cfs = self
|
||||
.status
|
||||
@@ -582,6 +586,7 @@ mod tests {
|
||||
pinned: false,
|
||||
store: None,
|
||||
ostree: None,
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
composefs: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ use std::collections::VecDeque;
|
||||
use std::io::IsTerminal;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use bootc_kernel_cmdline::Cmdline;
|
||||
use canon_json::CanonJsonSerialize;
|
||||
use fn_error_context::context;
|
||||
use ostree::glib;
|
||||
@@ -21,9 +19,9 @@ use ostree_ext::sysroot::SysrootLock;
|
||||
|
||||
use ostree_ext::ostree;
|
||||
|
||||
use crate::bootc_composefs::status::composefs_deployment_status;
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
use crate::bootc_composefs::status::{composefs_booted, composefs_deployment_status};
|
||||
use crate::cli::OutputFormat;
|
||||
use crate::composefs_consts::COMPOSEFS_CMDLINE;
|
||||
use crate::spec::ImageStatus;
|
||||
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType};
|
||||
use crate::spec::{ImageReference, ImageSignature};
|
||||
@@ -51,49 +49,6 @@ impl From<ImageSignature> for ostree_container::SignatureSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsed composefs command line
|
||||
pub(crate) struct ComposefsCmdline {
|
||||
#[allow(dead_code)]
|
||||
pub insecure: bool,
|
||||
pub digest: Box<str>,
|
||||
}
|
||||
|
||||
impl ComposefsCmdline {
|
||||
pub(crate) fn new(s: &str) -> Self {
|
||||
let (insecure, digest_str) = s
|
||||
.strip_prefix('?')
|
||||
.map(|v| (true, v))
|
||||
.unwrap_or_else(|| (false, s));
|
||||
ComposefsCmdline {
|
||||
insecure,
|
||||
digest: digest_str.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ComposefsCmdline {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let insecure = if self.insecure { "?" } else { "" };
|
||||
write!(f, "{}={}{}", COMPOSEFS_CMDLINE, insecure, self.digest)
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if we have composefs=<digest> in /proc/cmdline
|
||||
pub(crate) fn composefs_booted() -> Result<Option<&'static ComposefsCmdline>> {
|
||||
static CACHED_DIGEST_VALUE: OnceLock<Option<ComposefsCmdline>> = OnceLock::new();
|
||||
if let Some(v) = CACHED_DIGEST_VALUE.get() {
|
||||
return Ok(v.as_ref());
|
||||
}
|
||||
let cmdline = Cmdline::from_proc()?;
|
||||
let Some(kv) = cmdline.find_str(COMPOSEFS_CMDLINE) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(v) = kv.value else { return Ok(None) };
|
||||
let v = ComposefsCmdline::new(v);
|
||||
let r = CACHED_DIGEST_VALUE.get_or_init(|| Some(v));
|
||||
Ok(r.as_ref())
|
||||
}
|
||||
|
||||
/// Fixme lower serializability into ostree-ext
|
||||
fn transport_to_string(transport: ostree_container::Transport) -> String {
|
||||
match transport {
|
||||
@@ -255,6 +210,7 @@ fn boot_entry_from_deployment(
|
||||
deploy_serial: deployment.deployserial().try_into().unwrap(),
|
||||
stateroot: deployment.stateroot().into(),
|
||||
}),
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
composefs: None,
|
||||
};
|
||||
Ok(r)
|
||||
@@ -384,15 +340,9 @@ pub(crate) fn get_status(
|
||||
Ok((deployments, host))
|
||||
}
|
||||
|
||||
/// Implementation of the `bootc status` CLI command.
|
||||
#[context("Status")]
|
||||
pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
|
||||
match opts.format_version.unwrap_or_default() {
|
||||
// For historical reasons, both 0 and 1 mean "v1".
|
||||
0 | 1 => {}
|
||||
o => anyhow::bail!("Unsupported format version: {o}"),
|
||||
};
|
||||
let mut host = if ostree_booted()? {
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
async fn get_host() -> Result<Host> {
|
||||
let host = if ostree_booted()? {
|
||||
let sysroot = super::cli::get_storage().await?;
|
||||
let ostree = sysroot.get_ostree()?;
|
||||
let booted_deployment = ostree.booted_deployment();
|
||||
@@ -404,6 +354,34 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
Ok(host)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
async fn get_host() -> Result<Host> {
|
||||
let host = if ostree_booted()? {
|
||||
let sysroot = super::cli::get_storage().await?;
|
||||
let ostree = sysroot.get_ostree()?;
|
||||
let booted_deployment = ostree.booted_deployment();
|
||||
let (_deployments, host) = get_status(&ostree, booted_deployment.as_ref())?;
|
||||
host
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
Ok(host)
|
||||
}
|
||||
|
||||
/// Implementation of the `bootc status` CLI command.
|
||||
#[context("Status")]
|
||||
pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
|
||||
match opts.format_version.unwrap_or_default() {
|
||||
// For historical reasons, both 0 and 1 mean "v1".
|
||||
0 | 1 => {}
|
||||
o => anyhow::bail!("Unsupported format version: {o}"),
|
||||
};
|
||||
let mut host = get_host().await?;
|
||||
|
||||
// We could support querying the staged or rollback deployments
|
||||
// here too, but it's not a common use case at the moment.
|
||||
if opts.booted {
|
||||
@@ -537,6 +515,7 @@ fn human_render_slot(
|
||||
writeln!(out, "{digest} ({arch})")?;
|
||||
|
||||
// Write the EROFS verity if present
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
if let Some(composefs) = &entry.composefs {
|
||||
write_row_name(&mut out, "Verity", prefix_len)?;
|
||||
writeln!(out, "{}", composefs.verity)?;
|
||||
@@ -643,6 +622,7 @@ fn human_render_slot_ostree(
|
||||
}
|
||||
|
||||
/// Output a rendering of a non-container composefs boot entry.
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
fn human_render_slot_composefs(
|
||||
mut out: impl Write,
|
||||
slot: Slot,
|
||||
@@ -676,6 +656,8 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool)
|
||||
} else {
|
||||
writeln!(out)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
if let Some(image) = &host_status.image {
|
||||
human_render_slot(&mut out, Some(slot_name), host_status, image, verbose)?;
|
||||
} else if let Some(ostree) = host_status.ostree.as_ref() {
|
||||
@@ -691,6 +673,21 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool)
|
||||
} else {
|
||||
writeln!(out, "Current {slot_name} state is unknown")?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "composefs-backend"))]
|
||||
if let Some(image) = &host_status.image {
|
||||
human_render_slot(&mut out, Some(slot_name), host_status, image, verbose)?;
|
||||
} else if let Some(ostree) = host_status.ostree.as_ref() {
|
||||
human_render_slot_ostree(
|
||||
&mut out,
|
||||
Some(slot_name),
|
||||
host_status,
|
||||
&ostree.checksum,
|
||||
verbose,
|
||||
)?;
|
||||
} else {
|
||||
writeln!(out, "Current {slot_name} state is unknown")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,15 +879,4 @@ mod tests {
|
||||
assert!(w.contains("Commit:"));
|
||||
assert!(w.contains("Soft-reboot:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_composefs_parsing() {
|
||||
const DIGEST: &str = "8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52";
|
||||
let v = ComposefsCmdline::new(DIGEST);
|
||||
assert!(!v.insecure);
|
||||
assert_eq!(v.digest.as_ref(), DIGEST);
|
||||
let v = ComposefsCmdline::new(&format!("?{}", DIGEST));
|
||||
assert!(v.insecure);
|
||||
assert_eq!(v.digest.as_ref(), DIGEST);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::future::Future;
|
||||
use std::io::Write;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::{future::Future, path::Component};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use bootc_utils::CommandRunExt;
|
||||
@@ -190,6 +191,7 @@ pub(crate) fn digested_pullspec(image: &str, digest: &str) -> String {
|
||||
/// Computes a relative path from `from` to `to`.
|
||||
///
|
||||
/// Both `from` and `to` must be absolute paths.
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
pub(crate) fn path_relative_to(from: &Path, to: &Path) -> Result<PathBuf> {
|
||||
if !from.is_absolute() || !to.is_absolute() {
|
||||
anyhow::bail!("Paths must be absolute");
|
||||
@@ -248,6 +250,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "composefs-backend")]
|
||||
fn test_relative_path() {
|
||||
let from = Path::new("/sysroot/state/deploy/image_id");
|
||||
let to = Path::new("/sysroot/state/os/default/var");
|
||||
|
||||
Reference in New Issue
Block a user