1use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
13#[serde(deny_unknown_fields)]
14pub struct Boot {
15 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub kernel: Option<String>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub initrd: Option<String>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub cmdline: Option<String>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub dtb: Option<String>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub firmware: Option<Firmware>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub loader: Option<String>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub nvram: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub firmware_format: Option<FirmwareFormat>,
42 #[serde(default, skip_serializing_if = "Vec::is_empty")]
44 pub order: Vec<BootDevice>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub network: Option<NetworkBoot>,
49}
50
51impl Boot {
52 #[must_use]
55 pub fn beyond_firmware_format(&self) -> Vec<&'static str> {
56 let set = [
57 ("firmware", self.firmware == Some(Firmware::Bios)),
58 ("kernel", self.kernel.is_some()),
59 ("initrd", self.initrd.is_some()),
60 ("cmdline", self.cmdline.is_some()),
61 ("dtb", self.dtb.is_some()),
62 ("loader", self.loader.is_some()),
63 ("nvram", self.nvram.is_some()),
64 ("order", !self.order.is_empty()),
65 ("network", self.network.is_some()),
66 ];
67 set.into_iter().filter(|(_, on)| *on).map(|(name, _)| name).collect()
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73#[serde(rename_all = "lowercase")]
74pub enum Firmware {
75 Bios,
77 Uefi,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
83#[serde(rename_all = "lowercase")]
84pub enum FirmwareFormat {
85 Raw,
87 Qcow2,
89}
90
91impl FirmwareFormat {
92 #[must_use]
94 pub fn id(self) -> &'static str {
95 match self {
96 FirmwareFormat::Raw => "raw",
97 FirmwareFormat::Qcow2 => "qcow2",
98 }
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
104#[serde(rename_all = "lowercase")]
105pub enum BootDevice {
106 Hd,
108 Network,
110 Cdrom,
112}
113
114impl BootDevice {
115 #[must_use]
117 pub fn id(self) -> &'static str {
118 match self {
119 BootDevice::Hd => "hd",
120 BootDevice::Network => "network",
121 BootDevice::Cdrom => "cdrom",
122 }
123 }
124}
125
126#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
128#[serde(deny_unknown_fields)]
129pub struct NetworkBoot {
130 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub tftp_root: Option<String>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub file: Option<String>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub server: Option<String>,
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 fn beyond(yaml: &str) -> Result<Vec<&'static str>, serde_yaml_ng::Error> {
147 Ok(serde_yaml_ng::from_str::<Boot>(yaml)?.beyond_firmware_format())
148 }
149
150 #[test]
151 fn uefi_and_its_format_are_all_vagrants_qemu_provider_boots() -> Result<(), serde_yaml_ng::Error> {
152 assert_eq!(beyond("{ firmware: uefi, firmware_format: qcow2 }")?, Vec::<&str>::new());
153 assert_eq!(
154 beyond("{ firmware: bios, kernel: /k, initrd: /i, cmdline: quiet, dtb: /d, loader: /l, nvram: /n, order: [hd] }")?,
155 ["firmware", "kernel", "initrd", "cmdline", "dtb", "loader", "nvram", "order"]
156 );
157 Ok(())
158 }
159
160 #[test]
161 fn a_firmware_format_goes_by_the_name_a_spec_writes() -> Result<(), serde_yaml_ng::Error> {
162 for format in [FirmwareFormat::Raw, FirmwareFormat::Qcow2] {
163 assert_eq!(serde_yaml_ng::from_str::<FirmwareFormat>(format.id())?, format);
164 }
165 Ok(())
166 }
167}