Skip to content

Commit 1fa75d0

Browse files
authored
Merge pull request #288 from cgwalters/install-config-general-prep
install: Factor out configuration into new file
2 parents 6cfb370 + 9ce39b3 commit 1fa75d0

File tree

2 files changed

+133
-130
lines changed

2 files changed

+133
-130
lines changed

lib/src/install.rs

Lines changed: 1 addition & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// This sub-module is the "basic" installer that handles creating basic block device
88
// and filesystem setup.
99
pub(crate) mod baseline;
10+
pub(crate) mod config;
1011

1112
use std::io::BufWriter;
1213
use std::io::Write;
@@ -435,136 +436,6 @@ pub(crate) fn print_configuration() -> Result<()> {
435436
serde_json::to_writer(stdout, &install_config).map_err(Into::into)
436437
}
437438

438-
pub(crate) mod config {
439-
use super::*;
440-
441-
/// The toplevel config entry for installation configs stored
442-
/// in bootc/install (e.g. /etc/bootc/install/05-custom.toml)
443-
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
444-
#[serde(deny_unknown_fields)]
445-
pub(crate) struct InstallConfigurationToplevel {
446-
pub(crate) install: Option<InstallConfiguration>,
447-
}
448-
449-
/// The serialized [install] section
450-
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
451-
#[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)]
452-
pub(crate) struct InstallConfiguration {
453-
/// Root filesystem type
454-
pub(crate) root_fs_type: Option<super::baseline::Filesystem>,
455-
/// Kernel arguments, applied at installation time
456-
#[serde(skip_serializing_if = "Option::is_none")]
457-
pub(crate) kargs: Option<Vec<String>>,
458-
}
459-
460-
impl InstallConfiguration {
461-
/// Apply any values in other, overriding any existing values in `self`.
462-
fn merge(&mut self, other: Self) {
463-
fn mergeopt<T>(s: &mut Option<T>, o: Option<T>) {
464-
if let Some(o) = o {
465-
*s = Some(o);
466-
}
467-
}
468-
mergeopt(&mut self.root_fs_type, other.root_fs_type);
469-
if let Some(other_kargs) = other.kargs {
470-
self.kargs
471-
.get_or_insert_with(Default::default)
472-
.extend(other_kargs)
473-
}
474-
}
475-
476-
// Remove all configuration which is handled by `install to-filesystem`.
477-
pub(crate) fn filter_to_external(&mut self) {
478-
self.kargs.take();
479-
}
480-
}
481-
482-
#[context("Loading configuration")]
483-
/// Load the install configuration, merging all found configuration files.
484-
pub(crate) fn load_config() -> Result<InstallConfiguration> {
485-
const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["/usr/lib", "/usr/local/lib", "/etc", "/run"];
486-
let fragments =
487-
liboverdrop::scan(SYSTEMD_CONVENTIONAL_BASES, "bootc/install", &["toml"], true);
488-
let mut config: Option<InstallConfiguration> = None;
489-
for (_name, path) in fragments {
490-
let buf = std::fs::read_to_string(&path)?;
491-
let mut unused = std::collections::HashSet::new();
492-
let de = toml::Deserializer::new(&buf);
493-
let c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| {
494-
unused.insert(path.to_string());
495-
})
496-
.with_context(|| format!("Parsing {path:?}"))?;
497-
for key in unused {
498-
eprintln!("warning: {path:?}: Unknown key {key}");
499-
}
500-
if let Some(config) = config.as_mut() {
501-
if let Some(install) = c.install {
502-
tracing::debug!("Merging install config: {install:?}");
503-
config.merge(install);
504-
}
505-
} else {
506-
config = c.install;
507-
}
508-
}
509-
config.ok_or_else(|| anyhow::anyhow!("No bootc/install config found; this operating system must define a default configuration to be installable"))
510-
}
511-
512-
#[test]
513-
/// Verify that we can parse our default config file
514-
fn test_parse_config() {
515-
use super::baseline::Filesystem;
516-
517-
let c: InstallConfigurationToplevel = toml::from_str(
518-
r##"[install]
519-
root-fs-type = "xfs"
520-
"##,
521-
)
522-
.unwrap();
523-
let mut install = c.install.unwrap();
524-
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Xfs);
525-
let other = InstallConfigurationToplevel {
526-
install: Some(InstallConfiguration {
527-
root_fs_type: Some(Filesystem::Ext4),
528-
kargs: None,
529-
}),
530-
};
531-
install.merge(other.install.unwrap());
532-
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
533-
534-
let c: InstallConfigurationToplevel = toml::from_str(
535-
r##"[install]
536-
root-fs-type = "ext4"
537-
kargs = ["console=ttyS0", "foo=bar"]
538-
"##,
539-
)
540-
.unwrap();
541-
let mut install = c.install.unwrap();
542-
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
543-
let other = InstallConfigurationToplevel {
544-
install: Some(InstallConfiguration {
545-
root_fs_type: None,
546-
kargs: Some(
547-
["console=tty0", "nosmt"]
548-
.into_iter()
549-
.map(ToOwned::to_owned)
550-
.collect(),
551-
),
552-
}),
553-
};
554-
install.merge(other.install.unwrap());
555-
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
556-
assert_eq!(
557-
install.kargs,
558-
Some(
559-
["console=ttyS0", "foo=bar", "console=tty0", "nosmt"]
560-
.into_iter()
561-
.map(ToOwned::to_owned)
562-
.collect()
563-
)
564-
)
565-
}
566-
}
567-
568439
#[context("Creating ostree deployment")]
569440
async fn initialize_ostree_root_from_self(
570441
state: &State,

lib/src/install/config.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! # Configuration for `bootc install`
2+
//!
3+
//! This module handles the TOML configuration file for `bootc install`.
4+
5+
use anyhow::{Context, Result};
6+
use fn_error_context::context;
7+
use serde::{Deserialize, Serialize};
8+
9+
/// The toplevel config entry for installation configs stored
10+
/// in bootc/install (e.g. /etc/bootc/install/05-custom.toml)
11+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12+
#[serde(deny_unknown_fields)]
13+
pub(crate) struct InstallConfigurationToplevel {
14+
pub(crate) install: Option<InstallConfiguration>,
15+
}
16+
17+
/// The serialized [install] section
18+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
19+
#[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)]
20+
pub(crate) struct InstallConfiguration {
21+
/// Root filesystem type
22+
pub(crate) root_fs_type: Option<super::baseline::Filesystem>,
23+
/// Kernel arguments, applied at installation time
24+
#[serde(skip_serializing_if = "Option::is_none")]
25+
pub(crate) kargs: Option<Vec<String>>,
26+
}
27+
28+
impl InstallConfiguration {
29+
/// Apply any values in other, overriding any existing values in `self`.
30+
fn merge(&mut self, other: Self) {
31+
fn mergeopt<T>(s: &mut Option<T>, o: Option<T>) {
32+
if let Some(o) = o {
33+
*s = Some(o);
34+
}
35+
}
36+
mergeopt(&mut self.root_fs_type, other.root_fs_type);
37+
if let Some(other_kargs) = other.kargs {
38+
self.kargs
39+
.get_or_insert_with(Default::default)
40+
.extend(other_kargs)
41+
}
42+
}
43+
44+
// Remove all configuration which is handled by `install to-filesystem`.
45+
pub(crate) fn filter_to_external(&mut self) {
46+
self.kargs.take();
47+
}
48+
}
49+
50+
#[context("Loading configuration")]
51+
/// Load the install configuration, merging all found configuration files.
52+
pub(crate) fn load_config() -> Result<InstallConfiguration> {
53+
const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["/usr/lib", "/usr/local/lib", "/etc", "/run"];
54+
let fragments = liboverdrop::scan(SYSTEMD_CONVENTIONAL_BASES, "bootc/install", &["toml"], true);
55+
let mut config: Option<InstallConfiguration> = None;
56+
for (_name, path) in fragments {
57+
let buf = std::fs::read_to_string(&path)?;
58+
let mut unused = std::collections::HashSet::new();
59+
let de = toml::Deserializer::new(&buf);
60+
let c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| {
61+
unused.insert(path.to_string());
62+
})
63+
.with_context(|| format!("Parsing {path:?}"))?;
64+
for key in unused {
65+
eprintln!("warning: {path:?}: Unknown key {key}");
66+
}
67+
if let Some(config) = config.as_mut() {
68+
if let Some(install) = c.install {
69+
tracing::debug!("Merging install config: {install:?}");
70+
config.merge(install);
71+
}
72+
} else {
73+
config = c.install;
74+
}
75+
}
76+
config.ok_or_else(|| anyhow::anyhow!("No bootc/install config found; this operating system must define a default configuration to be installable"))
77+
}
78+
79+
#[test]
80+
/// Verify that we can parse our default config file
81+
fn test_parse_config() {
82+
use super::baseline::Filesystem;
83+
84+
let c: InstallConfigurationToplevel = toml::from_str(
85+
r##"[install]
86+
root-fs-type = "xfs"
87+
"##,
88+
)
89+
.unwrap();
90+
let mut install = c.install.unwrap();
91+
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Xfs);
92+
let other = InstallConfigurationToplevel {
93+
install: Some(InstallConfiguration {
94+
root_fs_type: Some(Filesystem::Ext4),
95+
kargs: None,
96+
}),
97+
};
98+
install.merge(other.install.unwrap());
99+
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
100+
101+
let c: InstallConfigurationToplevel = toml::from_str(
102+
r##"[install]
103+
root-fs-type = "ext4"
104+
kargs = ["console=ttyS0", "foo=bar"]
105+
"##,
106+
)
107+
.unwrap();
108+
let mut install = c.install.unwrap();
109+
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
110+
let other = InstallConfigurationToplevel {
111+
install: Some(InstallConfiguration {
112+
root_fs_type: None,
113+
kargs: Some(
114+
["console=tty0", "nosmt"]
115+
.into_iter()
116+
.map(ToOwned::to_owned)
117+
.collect(),
118+
),
119+
}),
120+
};
121+
install.merge(other.install.unwrap());
122+
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
123+
assert_eq!(
124+
install.kargs,
125+
Some(
126+
["console=ttyS0", "foo=bar", "console=tty0", "nosmt"]
127+
.into_iter()
128+
.map(ToOwned::to_owned)
129+
.collect()
130+
)
131+
)
132+
}

0 commit comments

Comments
 (0)