From 67a8df2ffce7688189ab83d60520c05a68c57b75 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 2 Dec 2025 12:49:40 +0100 Subject: [PATCH 1/2] cli: add `install print-configuration --all` When `install print-configuration` is run some options (notably the kargs) are currently filtered out. This makes sense because in general `bootc install to-filesystem` takes care of them. However with the recent work in image-builder/osbuild to use bootc containers directly as inputs to build ISOs [0],[1] we would like to get access to the kernel args too because when constructing a bootable ISO we also want to add the bootc container kargs. [0] https://github.com/orgs/osbuild/discussions/45 [1] https://github.com/osbuild/images/pull/1906 --- crates/lib/src/cli.rs | 4 ++-- crates/lib/src/install.rs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index 23bae71d9..c59b4dad1 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -289,7 +289,7 @@ pub(crate) enum InstallOpts { /// /// At the current time, the only output key is `root-fs-type` which is a string-valued /// filesystem name suitable for passing to `mkfs.$type`. - PrintConfiguration, + PrintConfiguration(crate::install::InstallPrintConfigurationOpts), } /// Subcommands which can be executed as part of a container build. @@ -1483,7 +1483,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> { crate::install::install_to_existing_root(opts).await } InstallOpts::Reset(opts) => crate::install::install_reset(opts).await, - InstallOpts::PrintConfiguration => crate::install::print_configuration(), + InstallOpts::PrintConfiguration(opts) => crate::install::print_configuration(opts), InstallOpts::EnsureCompletion {} => { let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?; crate::install::completion::run_from_anaconda(rootfs).await diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index a1e5b2b71..b68cbce8f 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -437,6 +437,15 @@ pub(crate) struct InstallResetOpts { karg: Option>, } +#[derive(Debug, clap::Parser, PartialEq, Eq)] +pub(crate) struct InstallPrintConfigurationOpts { + /// Print all configuration. + /// + /// Print configuration that is usually handled internally, like kargs. + #[clap(long)] + pub(crate) all: bool, +} + /// Global state captured from the container. #[derive(Debug, Clone)] pub(crate) struct SourceInfo { @@ -722,9 +731,11 @@ impl SourceInfo { } } -pub(crate) fn print_configuration() -> Result<()> { +pub(crate) fn print_configuration(opts: InstallPrintConfigurationOpts) -> Result<()> { let mut install_config = config::load_config()?.unwrap_or_default(); - install_config.filter_to_external(); + if !opts.all { + install_config.filter_to_external(); + } let stdout = std::io::stdout().lock(); anyhow::Ok(install_config.to_canon_json_writer(stdout)?) } From c78fa5cf6c59917ce84a57d788934a0560b9e28b Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 3 Dec 2025 12:20:25 +0100 Subject: [PATCH 2/2] cli: add `install print-configuration --root-dir` We would like to only inspect a container image with {bootc,}image-builder and not actually run the container. One reason is (too much?) paranoia, i.e. we want to eventually support bootc containers coming from the users that get passed into the service and not having to run anything on the container initially to generate the osbuild manifest minimizes the risk. So to still get the require parameters like preferred rootfs or kargs we would like to run our own bootc and then pass ``` bootc install print-configuration --root-dir /path/to/mounted/cnt ``` to generate the manifest. The actual build will still run the `boots install to-filesystem` from the container. But that step happens in an isolated instance that is destroyed after each build (we already do this for package based image builds as users can also inject custom content/rpms here). This PR implements this new "--root-dir" option. It also tweaks print_configuration to make it easier to test. With that we could drop parts of the tests for PR#1820 from the container.rs and move them in here. Signed-off-by: Michael Vogt --- crates/lib/src/install.rs | 62 ++++++++++++++++++++++++++++++-- crates/lib/src/install/config.rs | 14 ++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index b68cbce8f..809bc69ed 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -444,6 +444,10 @@ pub(crate) struct InstallPrintConfigurationOpts { /// Print configuration that is usually handled internally, like kargs. #[clap(long)] pub(crate) all: bool, + + /// Set an alternative rootdir + #[clap(long, default_value = "/")] + pub(crate) root_dir: Option, } /// Global state captured from the container. @@ -732,12 +736,20 @@ impl SourceInfo { } pub(crate) fn print_configuration(opts: InstallPrintConfigurationOpts) -> Result<()> { - let mut install_config = config::load_config()?.unwrap_or_default(); + let stdout = std::io::stdout().lock(); + print_configuration_to_writer(opts, stdout) +} + +fn print_configuration_to_writer( + opts: InstallPrintConfigurationOpts, + writer: W, +) -> Result<()> { + let root_dir = opts.root_dir.unwrap_or("/".to_string()); + let mut install_config = config::load_config_at(&root_dir)?.unwrap_or_default(); if !opts.all { install_config.filter_to_external(); } - let stdout = std::io::stdout().lock(); - anyhow::Ok(install_config.to_canon_json_writer(stdout)?) + anyhow::Ok(install_config.to_canon_json_writer(writer)?) } #[context("Creating ostree deployment")] @@ -2455,6 +2467,8 @@ pub(crate) async fn install_finalize(target: &Utf8Path) -> Result<()> { #[cfg(test)] mod tests { use super::*; + use std::fs; + use tempfile::tempdir; #[test] fn install_opts_serializable() { @@ -2584,4 +2598,46 @@ UUID=boot-uuid /boot ext4 defaults 0 0 Ok(()) } + + #[test] + fn test_print_configuration_with_root_dir() -> Result<()> { + use crate::install::config::{ + Filesystem, InstallConfiguration, InstallConfigurationToplevel, + }; + + let temp_dir = tempdir()?; + let root_path = temp_dir.path(); + + let config_dir = root_path.join("etc/bootc/install"); + fs::create_dir_all(&config_dir)?; + let config_path = config_dir.join("10-install.toml"); + + let test_config = InstallConfigurationToplevel { + install: Some(InstallConfiguration { + root_fs_type: Some(Filesystem::Xfs), + kargs: Some(vec!["quiet".to_string(), "karg2=2".to_string()]), + ..Default::default() + }), + }; + let toml_content = toml::to_string(&test_config)?; + fs::write(config_path, toml_content)?; + + let opts = InstallPrintConfigurationOpts { + root_dir: Some(root_path.to_str().unwrap().to_string()), + all: true, + }; + + let mut buffer = Vec::new(); + print_configuration_to_writer(opts, &mut buffer)?; + + let output_json = String::from_utf8(buffer)?; + let output_config: crate::install::config::InstallConfiguration = + serde_json::from_str(&output_json)?; + + let install_config = test_config.install.unwrap(); + assert_eq!(install_config.kargs, output_config.kargs); + assert_eq!(install_config.root_fs_type, output_config.root_fs_type); + + Ok(()) + } } diff --git a/crates/lib/src/install/config.rs b/crates/lib/src/install/config.rs index bdeecb459..633b4886e 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -6,6 +6,7 @@ use anyhow::{Context, Result}; use clap::ValueEnum; use fn_error_context::context; use serde::{Deserialize, Serialize}; +use std::path::Path; #[cfg(feature = "install-to-disk")] use super::baseline::BlockSetup; @@ -191,11 +192,20 @@ impl InstallConfiguration { #[context("Loading configuration")] /// Load the install configuration, merging all found configuration files. pub(crate) fn load_config() -> Result> { + load_config_at("/") +} + +pub(crate) fn load_config_at(root_dir: impl AsRef) -> Result> { let env = EnvProperties { sys_arch: std::env::consts::ARCH.to_string(), }; - const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["/usr/lib", "/usr/local/lib", "/etc", "/run"]; - let fragments = liboverdrop::scan(SYSTEMD_CONVENTIONAL_BASES, "bootc/install", &["toml"], true); + let root_dir = root_dir.as_ref(); + const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["usr/lib", "usr/local/lib", "etc", "run"]; + let systemd_conventional_bases = SYSTEMD_CONVENTIONAL_BASES + .iter() + .map(|v| root_dir.join(v)) + .collect::>(); + let fragments = liboverdrop::scan(systemd_conventional_bases, "bootc/install", &["toml"], true); let mut config: Option = None; for (_name, path) in fragments { let buf = std::fs::read_to_string(&path)?;