]> git.proxmox.com Git - pve-installer.git/blame - proxmox-auto-installer/src/answer.rs
cargo clippy --fix
[pve-installer.git] / proxmox-auto-installer / src / answer.rs
CommitLineData
9143507d 1use clap::ValueEnum;
c7edc2e1
AL
2use proxmox_installer_common::{
3 options::{BtrfsRaidLevel, FsType, ZfsChecksumOption, ZfsCompressOption, ZfsRaidLevel},
4 utils::{CidrAddress, Fqdn},
5};
6use serde::{Deserialize, Serialize};
7use std::{collections::BTreeMap, net::IpAddr};
8
0d555de1
WB
9// BTreeMap is used to store filters as the order of the filters will be stable, compared to
10// storing them in a HashMap
c7edc2e1
AL
11
12#[derive(Clone, Deserialize, Debug)]
20c927b7 13#[serde(rename_all = "kebab-case", deny_unknown_fields)]
c7edc2e1
AL
14pub struct Answer {
15 pub global: Global,
16 pub network: Network,
17 #[serde(rename = "disk-setup")]
18 pub disks: Disks,
19}
20
21#[derive(Clone, Deserialize, Debug)]
20c927b7 22#[serde(deny_unknown_fields)]
c7edc2e1
AL
23pub struct Global {
24 pub country: String,
25 pub fqdn: Fqdn,
26 pub keyboard: String,
27 pub mailto: String,
28 pub timezone: String,
77ca432f 29 pub root_password: String,
c7edc2e1
AL
30 #[serde(default)]
31 pub reboot_on_error: bool,
79bea2a7
CH
32 #[serde(default)]
33 pub root_ssh_keys: Vec<String>,
c7edc2e1
AL
34}
35
98036cb1
TL
36#[derive(Clone, Deserialize, Debug, Default, PartialEq)]
37#[serde(deny_unknown_fields)]
38enum NetworkConfigMode {
39 #[default]
40 #[serde(rename = "from-dhcp")]
41 FromDhcp,
42 #[serde(rename = "from-answer")]
43 FromAnswer,
44}
45
c7edc2e1 46#[derive(Clone, Deserialize, Debug)]
20c927b7 47#[serde(deny_unknown_fields)]
c7edc2e1
AL
48struct NetworkInAnswer {
49 #[serde(default)]
98036cb1 50 pub source: NetworkConfigMode,
c7edc2e1
AL
51 pub cidr: Option<CidrAddress>,
52 pub dns: Option<IpAddr>,
53 pub gateway: Option<IpAddr>,
54 pub filter: Option<BTreeMap<String, String>>,
55}
56
57#[derive(Clone, Deserialize, Debug)]
20c927b7 58#[serde(try_from = "NetworkInAnswer", deny_unknown_fields)]
c7edc2e1
AL
59pub struct Network {
60 pub network_settings: NetworkSettings,
61}
62
63impl TryFrom<NetworkInAnswer> for Network {
64 type Error = &'static str;
65
98036cb1
TL
66 fn try_from(network: NetworkInAnswer) -> Result<Self, Self::Error> {
67 if network.source == NetworkConfigMode::FromAnswer {
68 if network.cidr.is_none() {
c7edc2e1
AL
69 return Err("Field 'cidr' must be set.");
70 }
98036cb1 71 if network.dns.is_none() {
c7edc2e1
AL
72 return Err("Field 'dns' must be set.");
73 }
98036cb1 74 if network.gateway.is_none() {
c7edc2e1
AL
75 return Err("Field 'gateway' must be set.");
76 }
98036cb1 77 if network.filter.is_none() {
c7edc2e1
AL
78 return Err("Field 'filter' must be set.");
79 }
80
81 Ok(Network {
82 network_settings: NetworkSettings::Manual(NetworkManual {
98036cb1
TL
83 cidr: network.cidr.unwrap(),
84 dns: network.dns.unwrap(),
85 gateway: network.gateway.unwrap(),
86 filter: network.filter.unwrap(),
c7edc2e1
AL
87 }),
88 })
89 } else {
90 Ok(Network {
98036cb1 91 network_settings: NetworkSettings::FromDhcp,
c7edc2e1
AL
92 })
93 }
94 }
95}
96
97#[derive(Clone, Debug)]
98pub enum NetworkSettings {
98036cb1 99 FromDhcp,
c7edc2e1
AL
100 Manual(NetworkManual),
101}
102
103#[derive(Clone, Debug)]
104pub struct NetworkManual {
105 pub cidr: CidrAddress,
106 pub dns: IpAddr,
107 pub gateway: IpAddr,
108 pub filter: BTreeMap<String, String>,
109}
110
111#[derive(Clone, Debug, Deserialize)]
20c927b7 112#[serde(deny_unknown_fields)]
c7edc2e1
AL
113pub struct DiskSetup {
114 pub filesystem: Filesystem,
115 #[serde(default)]
116 pub disk_list: Vec<String>,
117 pub filter: Option<BTreeMap<String, String>>,
118 pub filter_match: Option<FilterMatch>,
119 pub zfs: Option<ZfsOptions>,
120 pub lvm: Option<LvmOptions>,
121 pub btrfs: Option<BtrfsOptions>,
122}
123
124#[derive(Clone, Debug, Deserialize)]
20c927b7 125#[serde(try_from = "DiskSetup", deny_unknown_fields)]
c7edc2e1
AL
126pub struct Disks {
127 pub fs_type: FsType,
128 pub disk_selection: DiskSelection,
129 pub filter_match: Option<FilterMatch>,
130 pub fs_options: FsOptions,
131}
132
133impl TryFrom<DiskSetup> for Disks {
134 type Error = &'static str;
135
136 fn try_from(source: DiskSetup) -> Result<Self, Self::Error> {
137 if source.disk_list.is_empty() && source.filter.is_none() {
138 return Err("Need either 'disk_list' or 'filter' set");
139 }
140 if !source.disk_list.is_empty() && source.filter.is_some() {
141 return Err("Cannot use both, 'disk_list' and 'filter'");
142 }
143
144 let disk_selection = if !source.disk_list.is_empty() {
145 DiskSelection::Selection(source.disk_list.clone())
146 } else {
147 DiskSelection::Filter(source.filter.clone().unwrap())
148 };
149
150 let lvm_checks = |source: &DiskSetup| -> Result<(), Self::Error> {
151 if source.zfs.is_some() || source.btrfs.is_some() {
152 return Err("make sure only 'lvm' options are set");
153 }
154 if source.disk_list.len() > 1 {
155 return Err("make sure to define only one disk for ext4 and xfs");
156 }
157 Ok(())
158 };
159 // TODO: improve checks for foreign FS options. E.g. less verbose and handling new FS types
160 // automatically
161 let (fs, fs_options) = match source.filesystem {
162 Filesystem::Xfs => {
163 lvm_checks(&source)?;
164 (
165 FsType::Xfs,
810c860d 166 FsOptions::LVM(source.lvm.unwrap_or_default()),
c7edc2e1
AL
167 )
168 }
169 Filesystem::Ext4 => {
170 lvm_checks(&source)?;
171 (
172 FsType::Ext4,
810c860d 173 FsOptions::LVM(source.lvm.unwrap_or_default()),
c7edc2e1
AL
174 )
175 }
176 Filesystem::Zfs => {
177 if source.lvm.is_some() || source.btrfs.is_some() {
178 return Err("make sure only 'zfs' options are set");
179 }
180 match source.zfs {
181 None | Some(ZfsOptions { raid: None, .. }) => {
182 return Err("ZFS raid level 'zfs.raid' must be set")
183 }
184 Some(opts) => (FsType::Zfs(opts.raid.unwrap()), FsOptions::ZFS(opts)),
185 }
186 }
187 Filesystem::Btrfs => {
188 if source.zfs.is_some() || source.lvm.is_some() {
189 return Err("make sure only 'btrfs' options are set");
190 }
191 match source.btrfs {
192 None | Some(BtrfsOptions { raid: None, .. }) => {
193 return Err("BTRFS raid level 'btrfs.raid' must be set")
194 }
195 Some(opts) => (FsType::Btrfs(opts.raid.unwrap()), FsOptions::BTRFS(opts)),
196 }
197 }
198 };
199
200 let res = Disks {
201 fs_type: fs,
202 disk_selection,
203 filter_match: source.filter_match,
204 fs_options,
205 };
206 Ok(res)
207 }
208}
209
210#[derive(Clone, Debug)]
211pub enum FsOptions {
212 LVM(LvmOptions),
213 ZFS(ZfsOptions),
214 BTRFS(BtrfsOptions),
215}
216
217#[derive(Clone, Debug)]
218pub enum DiskSelection {
219 Selection(Vec<String>),
220 Filter(BTreeMap<String, String>),
221}
9143507d 222#[derive(Clone, Deserialize, Debug, PartialEq, ValueEnum)]
20c927b7 223#[serde(rename_all = "lowercase", deny_unknown_fields)]
c7edc2e1
AL
224pub enum FilterMatch {
225 Any,
226 All,
227}
228
229#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
20c927b7 230#[serde(rename_all = "lowercase", deny_unknown_fields)]
c7edc2e1
AL
231pub enum Filesystem {
232 Ext4,
233 Xfs,
234 Zfs,
235 Btrfs,
236}
237
238#[derive(Clone, Copy, Default, Deserialize, Debug)]
20c927b7 239#[serde(deny_unknown_fields)]
c7edc2e1
AL
240pub struct ZfsOptions {
241 pub raid: Option<ZfsRaidLevel>,
242 pub ashift: Option<usize>,
243 pub arc_max: Option<usize>,
244 pub checksum: Option<ZfsChecksumOption>,
245 pub compress: Option<ZfsCompressOption>,
246 pub copies: Option<usize>,
247 pub hdsize: Option<f64>,
248}
249
250#[derive(Clone, Copy, Default, Deserialize, Serialize, Debug)]
20c927b7 251#[serde(deny_unknown_fields)]
c7edc2e1
AL
252pub struct LvmOptions {
253 pub hdsize: Option<f64>,
254 pub swapsize: Option<f64>,
255 pub maxroot: Option<f64>,
256 pub maxvz: Option<f64>,
257 pub minfree: Option<f64>,
258}
259
260#[derive(Clone, Copy, Default, Deserialize, Debug)]
20c927b7 261#[serde(deny_unknown_fields)]
c7edc2e1
AL
262pub struct BtrfsOptions {
263 pub hdsize: Option<f64>,
264 pub raid: Option<BtrfsRaidLevel>,
265}