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..809bc69ed 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -437,6 +437,19 @@ 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, + + /// Set an alternative rootdir + #[clap(long, default_value = "/")] + pub(crate) root_dir: Option, +} + /// Global state captured from the container. #[derive(Debug, Clone)] pub(crate) struct SourceInfo { @@ -722,11 +735,21 @@ impl SourceInfo { } } -pub(crate) fn print_configuration() -> Result<()> { - let mut install_config = config::load_config()?.unwrap_or_default(); - install_config.filter_to_external(); +pub(crate) fn print_configuration(opts: InstallPrintConfigurationOpts) -> Result<()> { let stdout = std::io::stdout().lock(); - anyhow::Ok(install_config.to_canon_json_writer(stdout)?) + 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(); + } + anyhow::Ok(install_config.to_canon_json_writer(writer)?) } #[context("Creating ostree deployment")] @@ -2444,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() { @@ -2573,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)?;