Skip to content

Commit 024aa19

Browse files
authored
Merge pull request #289 from cgwalters/generalize-install-config
install: Generalize `root-fs-type` into `install.filesystem.root.type`
2 parents 1fa75d0 + 558cd4b commit 024aa19

File tree

5 files changed

+200
-15
lines changed

5 files changed

+200
-15
lines changed

docs/install.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,15 @@ To enable `bootc install` as part of your OS/distribution base image,
103103
create a file named `/usr/lib/bootc/install/00-<osname>.toml` with the contents of the form:
104104

105105
```toml
106-
[install]
107-
root-fs-type = "xfs"
106+
[install.filesystem.root]
107+
type = "xfs"
108108
```
109109

110-
The `root-fs-type` value **MUST** be set.
110+
The `install.filesystem.root` value **MUST** be set.
111111

112112
Configuration files found in this directory will be merged, with higher alphanumeric values
113113
taking precedence. If for example you are building a derived container image from the above OS,
114-
you could create a `50-myos.toml` that sets `root-fs-type = "btrfs"` which will override the
114+
you could create a `50-myos.toml` that sets `type = "btrfs"` which will override the
115115
prior setting.
116116

117117
Other available options, also under the `[install]` section:
@@ -121,6 +121,8 @@ This option is particularly useful when creating derived/layered images; for exa
121121
image may want to have its default `console=` set, in contrast with a default base image.
122122
The values in this field are space separated.
123123

124+
`root-fs-type`: This value is the same as `install.filesystem.root.type`.
125+
124126
## Installing an "unconfigured" image
125127

126128
The bootc project aims to support generic/general-purpose operating

lib/src/install/baseline.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,10 @@ pub(crate) fn install_create_rootfs(
339339
// Initialize rootfs
340340
let root_filesystem = opts
341341
.filesystem
342-
.or(state.install_config.root_fs_type)
342+
.or(state
343+
.install_config
344+
.filesystem_root()
345+
.and_then(|r| r.fstype))
343346
.ok_or_else(|| anyhow::anyhow!("No root filesystem specified"))?;
344347
let root_uuid = mkfs(&rootdev, root_filesystem, Some("root"), [])?;
345348
let rootarg = format!("root=UUID={root_uuid}");

lib/src/install/config.rs

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,115 @@ pub(crate) struct InstallConfigurationToplevel {
1414
pub(crate) install: Option<InstallConfiguration>,
1515
}
1616

17+
/// Configuration for a filesystem
18+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
19+
#[serde(deny_unknown_fields)]
20+
pub(crate) struct RootFS {
21+
#[serde(rename = "type")]
22+
pub(crate) fstype: Option<super::baseline::Filesystem>,
23+
}
24+
25+
/// This structure should only define "system" or "basic" filesystems; we are
26+
/// not trying to generalize this into e.g. supporting `/var` or other ones.
27+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
28+
#[serde(deny_unknown_fields)]
29+
pub(crate) struct BasicFilesystems {
30+
pub(crate) root: Option<RootFS>,
31+
// TODO allow configuration of these other filesystems too
32+
// pub(crate) xbootldr: Option<FilesystemCustomization>,
33+
// pub(crate) esp: Option<FilesystemCustomization>,
34+
}
35+
1736
/// The serialized [install] section
1837
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1938
#[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)]
2039
pub(crate) struct InstallConfiguration {
2140
/// Root filesystem type
2241
pub(crate) root_fs_type: Option<super::baseline::Filesystem>,
42+
pub(crate) filesystem: Option<BasicFilesystems>,
2343
/// Kernel arguments, applied at installation time
2444
#[serde(skip_serializing_if = "Option::is_none")]
2545
pub(crate) kargs: Option<Vec<String>>,
2646
}
2747

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);
48+
fn merge_basic<T>(s: &mut Option<T>, o: Option<T>) {
49+
if let Some(o) = o {
50+
*s = Some(o);
51+
}
52+
}
53+
54+
trait Mergeable {
55+
fn merge(&mut self, other: Self)
56+
where
57+
Self: Sized;
58+
}
59+
60+
impl<T> Mergeable for Option<T>
61+
where
62+
T: Mergeable,
63+
{
64+
fn merge(&mut self, other: Self)
65+
where
66+
Self: Sized,
67+
{
68+
if let Some(other) = other {
69+
if let Some(s) = self.as_mut() {
70+
s.merge(other)
71+
} else {
72+
*self = Some(other);
3473
}
3574
}
36-
mergeopt(&mut self.root_fs_type, other.root_fs_type);
75+
}
76+
}
77+
78+
impl Mergeable for RootFS {
79+
/// Apply any values in other, overriding any existing values in `self`.
80+
fn merge(&mut self, other: Self) {
81+
merge_basic(&mut self.fstype, other.fstype)
82+
}
83+
}
84+
85+
impl Mergeable for BasicFilesystems {
86+
/// Apply any values in other, overriding any existing values in `self`.
87+
fn merge(&mut self, other: Self) {
88+
self.root.merge(other.root)
89+
}
90+
}
91+
92+
impl Mergeable for InstallConfiguration {
93+
/// Apply any values in other, overriding any existing values in `self`.
94+
fn merge(&mut self, other: Self) {
95+
merge_basic(&mut self.root_fs_type, other.root_fs_type);
96+
self.filesystem.merge(other.filesystem);
3797
if let Some(other_kargs) = other.kargs {
3898
self.kargs
3999
.get_or_insert_with(Default::default)
40100
.extend(other_kargs)
41101
}
42102
}
103+
}
104+
105+
impl InstallConfiguration {
106+
/// Some fields can be specified multiple ways. This synchronizes the values of the fields
107+
/// to ensure they're the same.
108+
///
109+
/// - install.root-fs-type is synchronized with install.filesystems.root.type; if
110+
/// both are set, then the latter takes precedence
111+
pub(crate) fn canonicalize(&mut self) {
112+
// New canonical form wins.
113+
if let Some(rootfs_type) = self.filesystem_root().and_then(|f| f.fstype.as_ref()) {
114+
self.root_fs_type = Some(*rootfs_type)
115+
} else if let Some(rootfs) = self.root_fs_type.as_ref() {
116+
let fs = self.filesystem.get_or_insert_with(Default::default);
117+
let root = fs.root.get_or_insert_with(Default::default);
118+
root.fstype = Some(*rootfs);
119+
}
120+
}
121+
122+
/// Convenience helper to access the root filesystem
123+
pub(crate) fn filesystem_root(&self) -> Option<&RootFS> {
124+
self.filesystem.as_ref().and_then(|fs| fs.root.as_ref())
125+
}
43126

44127
// Remove all configuration which is handled by `install to-filesystem`.
45128
pub(crate) fn filter_to_external(&mut self) {
@@ -73,7 +156,9 @@ pub(crate) fn load_config() -> Result<InstallConfiguration> {
73156
config = c.install;
74157
}
75158
}
76-
config.ok_or_else(|| anyhow::anyhow!("No bootc/install config found; this operating system must define a default configuration to be installable"))
159+
let mut config = config.ok_or_else(|| anyhow::anyhow!("No bootc/install config found; this operating system must define a default configuration to be installable"))?;
160+
config.canonicalize();
161+
Ok(config)
77162
}
78163

79164
#[test]
@@ -92,11 +177,23 @@ root-fs-type = "xfs"
92177
let other = InstallConfigurationToplevel {
93178
install: Some(InstallConfiguration {
94179
root_fs_type: Some(Filesystem::Ext4),
180+
filesystem: None,
95181
kargs: None,
96182
}),
97183
};
98184
install.merge(other.install.unwrap());
99-
assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
185+
assert_eq!(
186+
install.root_fs_type.as_ref().copied().unwrap(),
187+
Filesystem::Ext4
188+
);
189+
// This one shouldn't have been set
190+
assert!(install.filesystem_root().is_none());
191+
install.canonicalize();
192+
assert_eq!(install.root_fs_type.as_ref().unwrap(), &Filesystem::Ext4);
193+
assert_eq!(
194+
install.filesystem_root().unwrap().fstype.unwrap(),
195+
Filesystem::Ext4
196+
);
100197

101198
let c: InstallConfigurationToplevel = toml::from_str(
102199
r##"[install]
@@ -110,6 +207,7 @@ kargs = ["console=ttyS0", "foo=bar"]
110207
let other = InstallConfigurationToplevel {
111208
install: Some(InstallConfiguration {
112209
root_fs_type: None,
210+
filesystem: None,
113211
kargs: Some(
114212
["console=tty0", "nosmt"]
115213
.into_iter()
@@ -130,3 +228,35 @@ kargs = ["console=ttyS0", "foo=bar"]
130228
)
131229
)
132230
}
231+
232+
#[test]
233+
fn test_parse_filesystems() {
234+
use super::baseline::Filesystem;
235+
let c: InstallConfigurationToplevel = toml::from_str(
236+
r##"[install.filesystem.root]
237+
type = "xfs"
238+
"##,
239+
)
240+
.unwrap();
241+
let mut install = c.install.unwrap();
242+
assert_eq!(
243+
install.filesystem_root().unwrap().fstype.unwrap(),
244+
Filesystem::Xfs
245+
);
246+
let other = InstallConfigurationToplevel {
247+
install: Some(InstallConfiguration {
248+
root_fs_type: None,
249+
filesystem: Some(BasicFilesystems {
250+
root: Some(RootFS {
251+
fstype: Some(Filesystem::Ext4),
252+
}),
253+
}),
254+
kargs: None,
255+
}),
256+
};
257+
install.merge(other.install.unwrap());
258+
assert_eq!(
259+
install.filesystem_root().unwrap().fstype.unwrap(),
260+
Filesystem::Ext4
261+
);
262+
}

lib/src/privtests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ pub(crate) fn impl_run_container() -> Result<()> {
100100
assert!(stderr.contains("requires root privileges"));
101101

102102
let config = cmd!(sh, "bootc install print-configuration").read()?;
103-
let config: InstallConfiguration =
103+
let mut config: InstallConfiguration =
104104
serde_json::from_str(&config).context("Parsing install config")?;
105+
config.canonicalize();
105106
assert_eq!(
106107
config.root_fs_type.unwrap(),
107108
crate::install::baseline::Filesystem::Xfs
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
% bootc-install-config(5)
2+
3+
# NAME
4+
5+
bootc-install-config.toml
6+
7+
# DESCRIPTION
8+
9+
The `bootc install` process supports some basic customization. This configuration file
10+
is in TOML format, and will be discovered by the installation process in via "drop-in"
11+
files in `/usr/lib/bootc/install` that are processed in alphanumerical order.
12+
13+
The individual files are merged into a single final installation config, so it is
14+
supported for e.g. a container base image to provide a default root filesystem type,
15+
that can be overridden in a derived container image.
16+
17+
# install
18+
19+
This is the only defined toplevel table.
20+
21+
The `install`` section supports two subfields:
22+
23+
- `filesystem`: See below.
24+
- `kargs`: An array of strings; this will be appended to the set of kernel arguments.
25+
26+
# filesystem
27+
28+
There is one valid field:
29+
30+
- `root`: An instance of "filesystem-root"; see below
31+
32+
# filesystem-root
33+
34+
There is one valid field:
35+
36+
`type`: This can be any basic Linux filesystem with a `mkfs.$fstype`. For example, `ext4`, `xfs`, etc.
37+
38+
# Examples
39+
40+
```toml
41+
[install.filesystem.root]
42+
type = "xfs"
43+
[install]
44+
kargs = ["nosmt", "console=tty0"]
45+
```
46+
47+
# SEE ALSO
48+
49+
**bootc(1)**

0 commit comments

Comments
 (0)