From 9ce39b3c94e4b87aa7e9d1db9c0f25a3ff1b9563 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 29 Jan 2024 11:50:20 -0500 Subject: [PATCH] install: Factor out configuration into new file Our install config is going to grow; the "nested module in existing file" only scales to a certain point, and we are going to pass it. Signed-off-by: Colin Walters --- lib/src/install.rs | 131 +------------------------------------ lib/src/install/config.rs | 132 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 130 deletions(-) create mode 100644 lib/src/install/config.rs diff --git a/lib/src/install.rs b/lib/src/install.rs index f7f69148..a95f5a59 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -7,6 +7,7 @@ // This sub-module is the "basic" installer that handles creating basic block device // and filesystem setup. pub(crate) mod baseline; +pub(crate) mod config; use std::io::BufWriter; use std::io::Write; @@ -435,136 +436,6 @@ pub(crate) fn print_configuration() -> Result<()> { serde_json::to_writer(stdout, &install_config).map_err(Into::into) } -pub(crate) mod config { - use super::*; - - /// The toplevel config entry for installation configs stored - /// in bootc/install (e.g. /etc/bootc/install/05-custom.toml) - #[derive(Debug, Clone, Serialize, Deserialize, Default)] - #[serde(deny_unknown_fields)] - pub(crate) struct InstallConfigurationToplevel { - pub(crate) install: Option, - } - - /// The serialized [install] section - #[derive(Debug, Clone, Serialize, Deserialize, Default)] - #[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)] - pub(crate) struct InstallConfiguration { - /// Root filesystem type - pub(crate) root_fs_type: Option, - /// Kernel arguments, applied at installation time - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) kargs: Option>, - } - - impl InstallConfiguration { - /// Apply any values in other, overriding any existing values in `self`. - fn merge(&mut self, other: Self) { - fn mergeopt(s: &mut Option, o: Option) { - if let Some(o) = o { - *s = Some(o); - } - } - mergeopt(&mut self.root_fs_type, other.root_fs_type); - if let Some(other_kargs) = other.kargs { - self.kargs - .get_or_insert_with(Default::default) - .extend(other_kargs) - } - } - - // Remove all configuration which is handled by `install to-filesystem`. - pub(crate) fn filter_to_external(&mut self) { - self.kargs.take(); - } - } - - #[context("Loading configuration")] - /// Load the install configuration, merging all found configuration files. - pub(crate) fn load_config() -> Result { - const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["/usr/lib", "/usr/local/lib", "/etc", "/run"]; - 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)?; - let mut unused = std::collections::HashSet::new(); - let de = toml::Deserializer::new(&buf); - let c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| { - unused.insert(path.to_string()); - }) - .with_context(|| format!("Parsing {path:?}"))?; - for key in unused { - eprintln!("warning: {path:?}: Unknown key {key}"); - } - if let Some(config) = config.as_mut() { - if let Some(install) = c.install { - tracing::debug!("Merging install config: {install:?}"); - config.merge(install); - } - } else { - config = c.install; - } - } - config.ok_or_else(|| anyhow::anyhow!("No bootc/install config found; this operating system must define a default configuration to be installable")) - } - - #[test] - /// Verify that we can parse our default config file - fn test_parse_config() { - use super::baseline::Filesystem; - - let c: InstallConfigurationToplevel = toml::from_str( - r##"[install] -root-fs-type = "xfs" -"##, - ) - .unwrap(); - let mut install = c.install.unwrap(); - assert_eq!(install.root_fs_type.unwrap(), Filesystem::Xfs); - let other = InstallConfigurationToplevel { - install: Some(InstallConfiguration { - root_fs_type: Some(Filesystem::Ext4), - kargs: None, - }), - }; - install.merge(other.install.unwrap()); - assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); - - let c: InstallConfigurationToplevel = toml::from_str( - r##"[install] -root-fs-type = "ext4" -kargs = ["console=ttyS0", "foo=bar"] -"##, - ) - .unwrap(); - let mut install = c.install.unwrap(); - assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); - let other = InstallConfigurationToplevel { - install: Some(InstallConfiguration { - root_fs_type: None, - kargs: Some( - ["console=tty0", "nosmt"] - .into_iter() - .map(ToOwned::to_owned) - .collect(), - ), - }), - }; - install.merge(other.install.unwrap()); - assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); - assert_eq!( - install.kargs, - Some( - ["console=ttyS0", "foo=bar", "console=tty0", "nosmt"] - .into_iter() - .map(ToOwned::to_owned) - .collect() - ) - ) - } -} - #[context("Creating ostree deployment")] async fn initialize_ostree_root_from_self( state: &State, diff --git a/lib/src/install/config.rs b/lib/src/install/config.rs new file mode 100644 index 00000000..9a8a1347 --- /dev/null +++ b/lib/src/install/config.rs @@ -0,0 +1,132 @@ +//! # Configuration for `bootc install` +//! +//! This module handles the TOML configuration file for `bootc install`. + +use anyhow::{Context, Result}; +use fn_error_context::context; +use serde::{Deserialize, Serialize}; + +/// The toplevel config entry for installation configs stored +/// in bootc/install (e.g. /etc/bootc/install/05-custom.toml) +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(deny_unknown_fields)] +pub(crate) struct InstallConfigurationToplevel { + pub(crate) install: Option, +} + +/// The serialized [install] section +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)] +pub(crate) struct InstallConfiguration { + /// Root filesystem type + pub(crate) root_fs_type: Option, + /// Kernel arguments, applied at installation time + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) kargs: Option>, +} + +impl InstallConfiguration { + /// Apply any values in other, overriding any existing values in `self`. + fn merge(&mut self, other: Self) { + fn mergeopt(s: &mut Option, o: Option) { + if let Some(o) = o { + *s = Some(o); + } + } + mergeopt(&mut self.root_fs_type, other.root_fs_type); + if let Some(other_kargs) = other.kargs { + self.kargs + .get_or_insert_with(Default::default) + .extend(other_kargs) + } + } + + // Remove all configuration which is handled by `install to-filesystem`. + pub(crate) fn filter_to_external(&mut self) { + self.kargs.take(); + } +} + +#[context("Loading configuration")] +/// Load the install configuration, merging all found configuration files. +pub(crate) fn load_config() -> Result { + const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["/usr/lib", "/usr/local/lib", "/etc", "/run"]; + 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)?; + let mut unused = std::collections::HashSet::new(); + let de = toml::Deserializer::new(&buf); + let c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| { + unused.insert(path.to_string()); + }) + .with_context(|| format!("Parsing {path:?}"))?; + for key in unused { + eprintln!("warning: {path:?}: Unknown key {key}"); + } + if let Some(config) = config.as_mut() { + if let Some(install) = c.install { + tracing::debug!("Merging install config: {install:?}"); + config.merge(install); + } + } else { + config = c.install; + } + } + config.ok_or_else(|| anyhow::anyhow!("No bootc/install config found; this operating system must define a default configuration to be installable")) +} + +#[test] +/// Verify that we can parse our default config file +fn test_parse_config() { + use super::baseline::Filesystem; + + let c: InstallConfigurationToplevel = toml::from_str( + r##"[install] +root-fs-type = "xfs" +"##, + ) + .unwrap(); + let mut install = c.install.unwrap(); + assert_eq!(install.root_fs_type.unwrap(), Filesystem::Xfs); + let other = InstallConfigurationToplevel { + install: Some(InstallConfiguration { + root_fs_type: Some(Filesystem::Ext4), + kargs: None, + }), + }; + install.merge(other.install.unwrap()); + assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); + + let c: InstallConfigurationToplevel = toml::from_str( + r##"[install] +root-fs-type = "ext4" +kargs = ["console=ttyS0", "foo=bar"] +"##, + ) + .unwrap(); + let mut install = c.install.unwrap(); + assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); + let other = InstallConfigurationToplevel { + install: Some(InstallConfiguration { + root_fs_type: None, + kargs: Some( + ["console=tty0", "nosmt"] + .into_iter() + .map(ToOwned::to_owned) + .collect(), + ), + }), + }; + install.merge(other.install.unwrap()); + assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); + assert_eq!( + install.kargs, + Some( + ["console=ttyS0", "foo=bar", "console=tty0", "nosmt"] + .into_iter() + .map(ToOwned::to_owned) + .collect() + ) + ) +}