1use crate::spec::Bootloader;
6use anyhow::{Context, Result};
7use clap::ValueEnum;
8use fn_error_context::context;
9use serde::{Deserialize, Serialize};
10
11#[cfg(feature = "install-to-disk")]
12use super::baseline::BlockSetup;
13
14pub(crate) struct EnvProperties {
17 pub(crate) sys_arch: String,
18}
19
20#[derive(clap::ValueEnum, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "kebab-case")]
23pub(crate) enum Filesystem {
24 Xfs,
25 Ext4,
26 Btrfs,
27}
28
29impl std::fmt::Display for Filesystem {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 self.to_possible_value().unwrap().get_name().fmt(f)
32 }
33}
34
35impl TryFrom<&str> for Filesystem {
36 type Error = anyhow::Error;
37
38 fn try_from(value: &str) -> Result<Self, Self::Error> {
39 match value {
40 "xfs" => Ok(Self::Xfs),
41 "ext4" => Ok(Self::Ext4),
42 "btrfs" => Ok(Self::Btrfs),
43 other => anyhow::bail!("Unknown filesystem: {}", other),
44 }
45 }
46}
47
48impl Filesystem {
49 pub(crate) fn supports_fsverity(&self) -> bool {
50 matches!(self, Self::Ext4 | Self::Btrfs)
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, Default)]
57#[serde(deny_unknown_fields)]
58pub(crate) struct InstallConfigurationToplevel {
59 pub(crate) install: Option<InstallConfiguration>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, Default)]
64#[serde(deny_unknown_fields)]
65pub(crate) struct RootFS {
66 #[serde(rename = "type")]
67 pub(crate) fstype: Option<Filesystem>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, Default)]
73#[serde(deny_unknown_fields)]
74pub(crate) struct BasicFilesystems {
75 pub(crate) root: Option<RootFS>,
76 }
80
81pub(crate) type OstreeRepoOpts = ostree_ext::repo_options::RepoOptions;
83
84#[derive(Debug, Clone, Serialize, Deserialize, Default)]
86#[serde(rename_all = "kebab-case", deny_unknown_fields)]
87pub(crate) struct Bootupd {
88 pub(crate) skip_boot_uuid: Option<bool>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, Default)]
96#[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)]
97pub(crate) struct InstallConfiguration {
98 pub(crate) root_fs_type: Option<Filesystem>,
100 #[cfg(feature = "install-to-disk")]
102 pub(crate) block: Option<Vec<BlockSetup>>,
103 pub(crate) filesystem: Option<BasicFilesystems>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub(crate) kargs: Option<Vec<String>>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub(crate) karg_deletes: Option<Vec<String>>,
110 pub(crate) match_architectures: Option<Vec<String>>,
112 pub(crate) ostree: Option<OstreeRepoOpts>,
114 pub(crate) stateroot: Option<String>,
116 pub(crate) root_mount_spec: Option<String>,
119 pub(crate) boot_mount_spec: Option<String>,
121 pub(crate) bootupd: Option<Bootupd>,
123 pub(crate) bootloader: Option<Bootloader>,
125}
126
127fn merge_basic<T>(s: &mut Option<T>, o: Option<T>, _env: &EnvProperties) {
128 if let Some(o) = o {
129 *s = Some(o);
130 }
131}
132
133trait Mergeable {
134 fn merge(&mut self, other: Self, env: &EnvProperties)
135 where
136 Self: Sized;
137}
138
139impl<T> Mergeable for Option<T>
140where
141 T: Mergeable,
142{
143 fn merge(&mut self, other: Self, env: &EnvProperties)
144 where
145 Self: Sized,
146 {
147 if let Some(other) = other {
148 if let Some(s) = self.as_mut() {
149 s.merge(other, env)
150 } else {
151 *self = Some(other);
152 }
153 }
154 }
155}
156
157impl Mergeable for RootFS {
158 fn merge(&mut self, other: Self, env: &EnvProperties) {
160 merge_basic(&mut self.fstype, other.fstype, env)
161 }
162}
163
164impl Mergeable for BasicFilesystems {
165 fn merge(&mut self, other: Self, env: &EnvProperties) {
167 self.root.merge(other.root, env)
168 }
169}
170
171impl Mergeable for OstreeRepoOpts {
172 fn merge(&mut self, other: Self, env: &EnvProperties) {
174 merge_basic(
175 &mut self.bls_append_except_default,
176 other.bls_append_except_default,
177 env,
178 )
179 }
180}
181
182impl Mergeable for Bootupd {
183 fn merge(&mut self, other: Self, env: &EnvProperties) {
185 merge_basic(&mut self.skip_boot_uuid, other.skip_boot_uuid, env)
186 }
187}
188
189impl Mergeable for InstallConfiguration {
190 fn merge(&mut self, other: Self, env: &EnvProperties) {
192 if other
195 .match_architectures
196 .map(|a| a.contains(&env.sys_arch))
197 .unwrap_or(true)
198 {
199 merge_basic(&mut self.root_fs_type, other.root_fs_type, env);
200 #[cfg(feature = "install-to-disk")]
201 merge_basic(&mut self.block, other.block, env);
202 self.filesystem.merge(other.filesystem, env);
203 self.ostree.merge(other.ostree, env);
204 merge_basic(&mut self.stateroot, other.stateroot, env);
205 merge_basic(&mut self.root_mount_spec, other.root_mount_spec, env);
206 merge_basic(&mut self.boot_mount_spec, other.boot_mount_spec, env);
207 self.bootupd.merge(other.bootupd, env);
208 merge_basic(&mut self.bootloader, other.bootloader, env);
209 if let Some(other_kargs) = other.kargs {
210 self.kargs
211 .get_or_insert_with(Default::default)
212 .extend(other_kargs)
213 }
214 if let Some(other_karg_deletes) = other.karg_deletes {
215 self.karg_deletes
216 .get_or_insert_with(Default::default)
217 .extend(other_karg_deletes)
218 }
219 }
220 }
221}
222
223impl InstallConfiguration {
224 pub(crate) fn canonicalize(&mut self) {
230 if let Some(rootfs_type) = self.filesystem_root().and_then(|f| f.fstype.as_ref()) {
232 self.root_fs_type = Some(*rootfs_type)
233 } else if let Some(rootfs) = self.root_fs_type.as_ref() {
234 let fs = self.filesystem.get_or_insert_with(Default::default);
235 let root = fs.root.get_or_insert_with(Default::default);
236 root.fstype = Some(*rootfs);
237 }
238
239 #[cfg(feature = "install-to-disk")]
240 if self.block.is_none() {
241 self.block = Some(vec![BlockSetup::Direct]);
242 }
243 }
244
245 pub(crate) fn filesystem_root(&self) -> Option<&RootFS> {
247 self.filesystem.as_ref().and_then(|fs| fs.root.as_ref())
248 }
249
250 pub(crate) fn filter_to_external(&mut self) {
252 self.kargs.take();
253 self.karg_deletes.take();
254 }
255
256 #[cfg(feature = "install-to-disk")]
257 pub(crate) fn get_block_setup(&self, default: Option<BlockSetup>) -> Result<BlockSetup> {
258 let valid_block_setups = self.block.as_deref().unwrap_or_default();
259 let default_block = valid_block_setups.iter().next().ok_or_else(|| {
260 anyhow::anyhow!("Empty block storage configuration in install configuration")
261 })?;
262 let block_setup = default.as_ref().unwrap_or(default_block);
263 if !valid_block_setups.contains(block_setup) {
264 anyhow::bail!("Block setup {block_setup:?} is not enabled in installation config");
265 }
266 Ok(*block_setup)
267 }
268}
269
270#[context("Loading configuration")]
271pub(crate) fn load_config() -> Result<Option<InstallConfiguration>> {
273 let env = EnvProperties {
274 sys_arch: std::env::consts::ARCH.to_string(),
275 };
276 const SYSTEMD_CONVENTIONAL_BASES: &[&str] = &["/usr/lib", "/usr/local/lib", "/etc", "/run"];
277 let fragments = liboverdrop::scan(SYSTEMD_CONVENTIONAL_BASES, "bootc/install", &["toml"], true);
278 let mut config: Option<InstallConfiguration> = None;
279 for (_name, path) in fragments {
280 let buf = std::fs::read_to_string(&path)?;
281 let mut unused = std::collections::HashSet::new();
282 let de = toml::Deserializer::parse(&buf).with_context(|| format!("Parsing {path:?}"))?;
283 let mut c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| {
284 unused.insert(path.to_string());
285 })
286 .with_context(|| format!("Parsing {path:?}"))?;
287 for key in unused {
288 eprintln!("warning: {path:?}: Unknown key {key}");
289 }
290 if let Some(config) = config.as_mut() {
291 if let Some(install) = c.install {
292 tracing::debug!("Merging install config: {install:?}");
293 config.merge(install, &env);
294 }
295 } else {
296 if let Some(ref mut install) = c.install {
299 if install
300 .match_architectures
301 .as_ref()
302 .map(|a| a.contains(&env.sys_arch))
303 .unwrap_or(true)
304 {
305 config = c.install;
306 }
307 }
308 }
309 }
310 if let Some(config) = config.as_mut() {
311 config.canonicalize();
312 }
313 Ok(config)
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_parse_config() {
323 let env = EnvProperties {
324 sys_arch: "x86_64".to_string(),
325 };
326 let c: InstallConfigurationToplevel = toml::from_str(
327 r##"[install]
328root-fs-type = "xfs"
329"##,
330 )
331 .unwrap();
332 let mut install = c.install.unwrap();
333 assert_eq!(install.root_fs_type.unwrap(), Filesystem::Xfs);
334 let other = InstallConfigurationToplevel {
335 install: Some(InstallConfiguration {
336 root_fs_type: Some(Filesystem::Ext4),
337 ..Default::default()
338 }),
339 };
340 install.merge(other.install.unwrap(), &env);
341 assert_eq!(
342 install.root_fs_type.as_ref().copied().unwrap(),
343 Filesystem::Ext4
344 );
345 assert!(install.filesystem_root().is_none());
347 install.canonicalize();
348 assert_eq!(install.root_fs_type.as_ref().unwrap(), &Filesystem::Ext4);
349 assert_eq!(
350 install.filesystem_root().unwrap().fstype.unwrap(),
351 Filesystem::Ext4
352 );
353
354 let c: InstallConfigurationToplevel = toml::from_str(
355 r##"[install]
356root-fs-type = "ext4"
357kargs = ["console=ttyS0", "foo=bar"]
358karg-deletes = ["debug", "bar=baz"]
359"##,
360 )
361 .unwrap();
362 let mut install = c.install.unwrap();
363 assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
364 let other = InstallConfigurationToplevel {
365 install: Some(InstallConfiguration {
366 kargs: Some(
367 ["console=tty0", "nosmt"]
368 .into_iter()
369 .map(ToOwned::to_owned)
370 .collect(),
371 ),
372 karg_deletes: Some(
373 ["baz", "bar=baz"]
374 .into_iter()
375 .map(ToOwned::to_owned)
376 .collect(),
377 ),
378 ..Default::default()
379 }),
380 };
381 install.merge(other.install.unwrap(), &env);
382 assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4);
383 assert_eq!(
384 install.kargs,
385 Some(
386 ["console=ttyS0", "foo=bar", "console=tty0", "nosmt"]
387 .into_iter()
388 .map(ToOwned::to_owned)
389 .collect()
390 )
391 );
392 assert_eq!(
393 install.karg_deletes,
394 Some(
395 ["debug", "bar=baz", "baz", "bar=baz"]
396 .into_iter()
397 .map(ToOwned::to_owned)
398 .collect()
399 )
400 );
401 }
402
403 #[test]
404 fn test_parse_filesystems() {
405 let env = EnvProperties {
406 sys_arch: "x86_64".to_string(),
407 };
408 let c: InstallConfigurationToplevel = toml::from_str(
409 r##"[install.filesystem.root]
410type = "xfs"
411"##,
412 )
413 .unwrap();
414 let mut install = c.install.unwrap();
415 assert_eq!(
416 install.filesystem_root().unwrap().fstype.unwrap(),
417 Filesystem::Xfs
418 );
419 let other = InstallConfigurationToplevel {
420 install: Some(InstallConfiguration {
421 filesystem: Some(BasicFilesystems {
422 root: Some(RootFS {
423 fstype: Some(Filesystem::Ext4),
424 }),
425 }),
426 ..Default::default()
427 }),
428 };
429 install.merge(other.install.unwrap(), &env);
430 assert_eq!(
431 install.filesystem_root().unwrap().fstype.unwrap(),
432 Filesystem::Ext4
433 );
434 }
435
436 #[test]
437 fn test_parse_block() {
438 let env = EnvProperties {
439 sys_arch: "x86_64".to_string(),
440 };
441 let c: InstallConfigurationToplevel = toml::from_str(
442 r##"[install.filesystem.root]
443type = "xfs"
444"##,
445 )
446 .unwrap();
447 let mut install = c.install.unwrap();
448 {
450 let mut install = install.clone();
451 install.canonicalize();
452 assert_eq!(install.get_block_setup(None).unwrap(), BlockSetup::Direct);
453 }
454 let other = InstallConfigurationToplevel {
455 install: Some(InstallConfiguration {
456 block: Some(vec![]),
457 ..Default::default()
458 }),
459 };
460 install.merge(other.install.unwrap(), &env);
461 assert_eq!(install.block.as_ref().unwrap().len(), 0);
463 assert!(install.get_block_setup(None).is_err());
464
465 let c: InstallConfigurationToplevel = toml::from_str(
466 r##"[install]
467block = ["tpm2-luks"]"##,
468 )
469 .unwrap();
470 let mut install = c.install.unwrap();
471 install.canonicalize();
472 assert_eq!(install.block.as_ref().unwrap().len(), 1);
473 assert_eq!(install.get_block_setup(None).unwrap(), BlockSetup::Tpm2Luks);
474
475 assert!(install.get_block_setup(Some(BlockSetup::Direct)).is_err());
477 }
478
479 #[test]
480 fn test_arch() {
482 let env = EnvProperties {
484 sys_arch: "x86_64".to_string(),
485 };
486 let c: InstallConfigurationToplevel = toml::from_str(
487 r##"[install]
488root-fs-type = "xfs"
489"##,
490 )
491 .unwrap();
492 let mut install = c.install.unwrap();
493 let other = InstallConfigurationToplevel {
494 install: Some(InstallConfiguration {
495 kargs: Some(
496 ["console=tty0", "nosmt"]
497 .into_iter()
498 .map(ToOwned::to_owned)
499 .collect(),
500 ),
501 ..Default::default()
502 }),
503 };
504 install.merge(other.install.unwrap(), &env);
505 assert_eq!(
506 install.kargs,
507 Some(
508 ["console=tty0", "nosmt"]
509 .into_iter()
510 .map(ToOwned::to_owned)
511 .collect()
512 )
513 );
514 let env = EnvProperties {
515 sys_arch: "aarch64".to_string(),
516 };
517 let c: InstallConfigurationToplevel = toml::from_str(
518 r##"[install]
519root-fs-type = "xfs"
520"##,
521 )
522 .unwrap();
523 let mut install = c.install.unwrap();
524 let other = InstallConfigurationToplevel {
525 install: Some(InstallConfiguration {
526 kargs: Some(
527 ["console=tty0", "nosmt"]
528 .into_iter()
529 .map(ToOwned::to_owned)
530 .collect(),
531 ),
532 ..Default::default()
533 }),
534 };
535 install.merge(other.install.unwrap(), &env);
536 assert_eq!(
537 install.kargs,
538 Some(
539 ["console=tty0", "nosmt"]
540 .into_iter()
541 .map(ToOwned::to_owned)
542 .collect()
543 )
544 );
545
546 let env = EnvProperties {
548 sys_arch: "aarch64".to_string(),
549 };
550 let c: InstallConfigurationToplevel = toml::from_str(
551 r##"[install]
552root-fs-type = "xfs"
553"##,
554 )
555 .unwrap();
556 let mut install = c.install.unwrap();
557 let other = InstallConfigurationToplevel {
558 install: Some(InstallConfiguration {
559 kargs: Some(
560 ["console=ttyS0", "foo=bar"]
561 .into_iter()
562 .map(ToOwned::to_owned)
563 .collect(),
564 ),
565 match_architectures: Some(["x86_64"].into_iter().map(ToOwned::to_owned).collect()),
566 ..Default::default()
567 }),
568 };
569 install.merge(other.install.unwrap(), &env);
570 assert_eq!(install.kargs, None);
571 let other = InstallConfigurationToplevel {
572 install: Some(InstallConfiguration {
573 kargs: Some(
574 ["console=tty0", "nosmt"]
575 .into_iter()
576 .map(ToOwned::to_owned)
577 .collect(),
578 ),
579 match_architectures: Some(["aarch64"].into_iter().map(ToOwned::to_owned).collect()),
580 ..Default::default()
581 }),
582 };
583 install.merge(other.install.unwrap(), &env);
584 assert_eq!(
585 install.kargs,
586 Some(
587 ["console=tty0", "nosmt"]
588 .into_iter()
589 .map(ToOwned::to_owned)
590 .collect()
591 )
592 );
593
594 let env = EnvProperties {
596 sys_arch: "x86_64".to_string(),
597 };
598 let c: InstallConfigurationToplevel = toml::from_str(
599 r##"[install]
600root-fs-type = "xfs"
601"##,
602 )
603 .unwrap();
604 let mut install = c.install.unwrap();
605 let other = InstallConfigurationToplevel {
606 install: Some(InstallConfiguration {
607 kargs: Some(
608 ["console=tty0", "nosmt"]
609 .into_iter()
610 .map(ToOwned::to_owned)
611 .collect(),
612 ),
613 match_architectures: Some(
614 ["x86_64", "aarch64"]
615 .into_iter()
616 .map(ToOwned::to_owned)
617 .collect(),
618 ),
619 ..Default::default()
620 }),
621 };
622 install.merge(other.install.unwrap(), &env);
623 assert_eq!(
624 install.kargs,
625 Some(
626 ["console=tty0", "nosmt"]
627 .into_iter()
628 .map(ToOwned::to_owned)
629 .collect()
630 )
631 );
632 let env = EnvProperties {
633 sys_arch: "aarch64".to_string(),
634 };
635 let c: InstallConfigurationToplevel = toml::from_str(
636 r##"[install]
637root-fs-type = "xfs"
638"##,
639 )
640 .unwrap();
641 let mut install = c.install.unwrap();
642 let other = InstallConfigurationToplevel {
643 install: Some(InstallConfiguration {
644 kargs: Some(
645 ["console=tty0", "nosmt"]
646 .into_iter()
647 .map(ToOwned::to_owned)
648 .collect(),
649 ),
650 match_architectures: Some(
651 ["x86_64", "aarch64"]
652 .into_iter()
653 .map(ToOwned::to_owned)
654 .collect(),
655 ),
656 ..Default::default()
657 }),
658 };
659 install.merge(other.install.unwrap(), &env);
660 assert_eq!(
661 install.kargs,
662 Some(
663 ["console=tty0", "nosmt"]
664 .into_iter()
665 .map(ToOwned::to_owned)
666 .collect()
667 )
668 );
669 }
670
671 #[test]
672 fn test_parse_ostree() {
673 let env = EnvProperties {
674 sys_arch: "x86_64".to_string(),
675 };
676
677 let parse_cases = [
679 ("console=ttyS0", "console=ttyS0"),
680 ("console=ttyS0,115200n8", "console=ttyS0,115200n8"),
681 ("rd.lvm.lv=vg/root", "rd.lvm.lv=vg/root"),
682 ];
683 for (input, expected) in parse_cases {
684 let toml_str = format!(
685 r#"[install.ostree]
686bls-append-except-default = "{input}"
687"#
688 );
689 let c: InstallConfigurationToplevel = toml::from_str(&toml_str).unwrap();
690 assert_eq!(
691 c.install
692 .unwrap()
693 .ostree
694 .unwrap()
695 .bls_append_except_default
696 .unwrap(),
697 expected
698 );
699 }
700
701 let mut install: InstallConfiguration = toml::from_str(
703 r#"[ostree]
704bls-append-except-default = "console=ttyS0"
705"#,
706 )
707 .unwrap();
708 let other = InstallConfiguration {
709 ostree: Some(OstreeRepoOpts {
710 bls_append_except_default: Some("console=tty0".to_string()),
711 ..Default::default()
712 }),
713 ..Default::default()
714 };
715 install.merge(other, &env);
716 assert_eq!(
717 install.ostree.unwrap().bls_append_except_default.unwrap(),
718 "console=tty0"
719 );
720 }
721
722 #[test]
723 fn test_parse_stateroot() {
724 let c: InstallConfigurationToplevel = toml::from_str(
725 r#"[install]
726stateroot = "custom"
727"#,
728 )
729 .unwrap();
730 assert_eq!(c.install.unwrap().stateroot.unwrap(), "custom");
731 }
732
733 #[test]
734 fn test_merge_stateroot() {
735 let env = EnvProperties {
736 sys_arch: "x86_64".to_string(),
737 };
738 let mut install: InstallConfiguration = toml::from_str(
739 r#"stateroot = "original"
740"#,
741 )
742 .unwrap();
743 let other = InstallConfiguration {
744 stateroot: Some("newroot".to_string()),
745 ..Default::default()
746 };
747 install.merge(other, &env);
748 assert_eq!(install.stateroot.unwrap(), "newroot");
749 }
750
751 #[test]
752 fn test_parse_mount_specs() {
753 let c: InstallConfigurationToplevel = toml::from_str(
754 r#"[install]
755root-mount-spec = "LABEL=rootfs"
756boot-mount-spec = "UUID=abcd-1234"
757"#,
758 )
759 .unwrap();
760 let install = c.install.unwrap();
761 assert_eq!(install.root_mount_spec.unwrap(), "LABEL=rootfs");
762 assert_eq!(install.boot_mount_spec.unwrap(), "UUID=abcd-1234");
763 }
764
765 #[test]
766 fn test_merge_mount_specs() {
767 let env = EnvProperties {
768 sys_arch: "x86_64".to_string(),
769 };
770 let mut install: InstallConfiguration = toml::from_str(
771 r#"root-mount-spec = "UUID=old"
772boot-mount-spec = "UUID=oldboot"
773"#,
774 )
775 .unwrap();
776 let other = InstallConfiguration {
777 root_mount_spec: Some("LABEL=newroot".to_string()),
778 ..Default::default()
779 };
780 install.merge(other, &env);
781 assert_eq!(install.root_mount_spec.as_deref().unwrap(), "LABEL=newroot");
783 assert_eq!(install.boot_mount_spec.as_deref().unwrap(), "UUID=oldboot");
785 }
786
787 #[test]
790 fn test_parse_empty_mount_specs() {
791 let c: InstallConfigurationToplevel = toml::from_str(
792 r#"[install]
793root-mount-spec = ""
794boot-mount-spec = ""
795"#,
796 )
797 .unwrap();
798 let install = c.install.unwrap();
799 assert_eq!(install.root_mount_spec.as_deref().unwrap(), "");
800 assert_eq!(install.boot_mount_spec.as_deref().unwrap(), "");
801 }
802
803 #[test]
804 fn test_parse_bootupd_skip_boot_uuid() {
805 let c: InstallConfigurationToplevel = toml::from_str(
807 r#"[install.bootupd]
808skip-boot-uuid = true
809"#,
810 )
811 .unwrap();
812 assert_eq!(
813 c.install.unwrap().bootupd.unwrap().skip_boot_uuid.unwrap(),
814 true
815 );
816
817 let c: InstallConfigurationToplevel = toml::from_str(
819 r#"[install.bootupd]
820skip-boot-uuid = false
821"#,
822 )
823 .unwrap();
824 assert_eq!(
825 c.install.unwrap().bootupd.unwrap().skip_boot_uuid.unwrap(),
826 false
827 );
828
829 let c: InstallConfigurationToplevel = toml::from_str(
831 r#"[install]
832root-fs-type = "xfs"
833"#,
834 )
835 .unwrap();
836 assert!(c.install.unwrap().bootupd.is_none());
837 }
838
839 #[test]
840 fn test_merge_bootupd_skip_boot_uuid() {
841 let env = EnvProperties {
842 sys_arch: "x86_64".to_string(),
843 };
844 let mut install: InstallConfiguration = toml::from_str(
845 r#"[bootupd]
846skip-boot-uuid = false
847"#,
848 )
849 .unwrap();
850 let other = InstallConfiguration {
851 bootupd: Some(Bootupd {
852 skip_boot_uuid: Some(true),
853 }),
854 ..Default::default()
855 };
856 install.merge(other, &env);
857 assert_eq!(install.bootupd.unwrap().skip_boot_uuid.unwrap(), true);
859 }
860}
861
862#[test]
863fn test_parse_bootloader() {
864 let env = EnvProperties {
865 sys_arch: "x86_64".to_string(),
866 };
867
868 let c: InstallConfigurationToplevel = toml::from_str(
870 r##"[install]
871bootloader = "none"
872"##,
873 )
874 .unwrap();
875 assert_eq!(c.install.unwrap().bootloader, Some(Bootloader::None));
876
877 let c: InstallConfigurationToplevel = toml::from_str(
879 r##"[install]
880bootloader = "grub"
881"##,
882 )
883 .unwrap();
884 assert_eq!(c.install.unwrap().bootloader, Some(Bootloader::Grub));
885
886 let mut install: InstallConfiguration = toml::from_str(
889 r#"bootloader = "systemd"
890"#,
891 )
892 .unwrap();
893
894 let other = InstallConfiguration {
896 bootloader: Some(Bootloader::None),
897 ..Default::default()
898 };
899
900 install.merge(other, &env);
902 assert_eq!(install.bootloader, Some(Bootloader::None));
903}