diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 1e948ac9e..7a08598ad 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; use std::io::Write; use std::os::fd::{AsFd, AsRawFd}; use std::os::unix::process::CommandExt; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process; use std::process::Command; use std::str::FromStr; @@ -453,6 +453,13 @@ pub(crate) struct InstallPrintConfigurationOpts { /// Print configuration that is usually handled internally, like kargs. #[clap(long)] pub(crate) all: bool, + + /// Set an alternative root filesystem. + /// + /// This can be used as part of a multi-stage build operation to detect + /// the desired configuration before executing code from a target container image. + #[clap(long, default_value = "/")] + pub(crate) sysroot: PathBuf, } /// Global state captured from the container. @@ -742,12 +749,19 @@ 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 mut install_config = config::load_config_at(&opts.sysroot)?.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")] @@ -2517,6 +2531,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() { @@ -2721,4 +2737,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 { + sysroot: root_path.to_owned(), + 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)?; diff --git a/docs/src/man/bootc-install-print-configuration.8.md b/docs/src/man/bootc-install-print-configuration.8.md index 67928c9f1..20b58fd7d 100644 --- a/docs/src/man/bootc-install-print-configuration.8.md +++ b/docs/src/man/bootc-install-print-configuration.8.md @@ -26,6 +26,12 @@ string-valued filesystem name suitable for passing to `mkfs.\$type`. Print all configuration +**--sysroot**=*SYSROOT* + + Set an alternative root filesystem + + Default: / + # VERSION