|
1 | 1 | mod checkers; |
2 | 2 | mod helpers; |
| 3 | +mod postinstall_cmd; |
| 4 | +mod preinstall_cmd; |
3 | 5 | mod recorders; |
4 | 6 |
|
5 | | -use checkers::preinstall::{ |
6 | | - byo_kernel::BYOKernelChecks, iommu::IOMMUChecks, kernel::KernelChecks, numa::NUMAChecks, |
7 | | - pvh::PVHChecks, system::SystemChecks, |
8 | | -}; |
9 | | - |
10 | | -use checkers::postinstall::{ |
11 | | - guest_type::GuestTypeChecks, kernel::PostinstallKernelChecks, kube::KubeChecks, |
12 | | - services::ServiceChecks, |
13 | | -}; |
14 | | - |
15 | 7 | use clap::{Parser, Subcommand}; |
16 | 8 | use console::{Emoji, style}; |
17 | | -use helpers::{ |
18 | | - CheckGroup, CheckGroupCategory, CheckGroupResult, |
19 | | - CheckResultValue::{Errored, Failed, Passed}, |
20 | | - host_executor::HostNamespaceExecutor, |
21 | | -}; |
22 | | -use recorders::postinstall::system::SystemRecorder as postrecorder; |
23 | | -use recorders::preinstall::system::SystemRecorder as prerecorder; |
| 9 | +use helpers::{CheckGroup, CheckGroupResult, host_executor::HostNamespaceExecutor}; |
24 | 10 |
|
25 | | -use anyhow::{Context, Result, anyhow, bail}; |
| 11 | +use anyhow::{Context, Result}; |
26 | 12 | use chrono::Utc; |
27 | 13 | use flate2::{Compression, write::GzEncoder}; |
28 | 14 | use log::debug; |
29 | 15 | use std::{ |
30 | | - collections::HashSet, |
31 | | - env, fs, |
| 16 | + fs, |
32 | 17 | fs::File, |
33 | 18 | path::{Path, PathBuf}, |
34 | | - process, |
35 | 19 | }; |
36 | 20 | use tokio::task::JoinHandle; |
37 | 21 |
|
@@ -95,268 +79,14 @@ async fn main() -> Result<()> { |
95 | 79 | only_checks, |
96 | 80 | report_dir, |
97 | 81 | } => { |
98 | | - // If we are in a privileged container running in the host pid namespace, |
99 | | - // this creates a tokio thread pool that runs stuff outside of the container context, |
100 | | - // directly on the host. |
101 | | - // If we are in a regular old `sudo`'d binary running naked on the host, |
102 | | - // this is effectively a silent no-op. |
103 | | - let host_executor = HostNamespaceExecutor::new(); |
104 | | - |
105 | | - // See if we are already booted under Edera. If so, error out and suggest `postinstall` |
106 | | - // as the command to run. |
107 | | - match host_executor |
108 | | - .spawn_in_host_ns(async { |
109 | | - if !Path::new("/var/lib/edera/protect/.install-completed").exists() { |
110 | | - return false; |
111 | | - } |
112 | | - let xen = Path::new("/sys/hypervisor/type"); |
113 | | - xen.exists() && fs::read_to_string(xen).unwrap_or_default().trim() == "xen" |
114 | | - }) |
| 82 | + preinstall_cmd::do_preinstall(byo_kernel, record_hostinfo, only_checks, report_dir) |
115 | 83 | .await |
116 | | - { |
117 | | - // TODO(bml) later we may add a `postinstall` command, |
118 | | - // but for now all we have is `preinstall` and running it under an active Edera boot |
119 | | - // is not supported or useful. |
120 | | - Ok(true) => { |
121 | | - println!( |
122 | | - "{}", |
123 | | - style("Edera is already installed. Run `edera-check postinstall` instead.") |
124 | | - .red() |
125 | | - .bold() |
126 | | - ); |
127 | | - process::exit(1); |
128 | | - } |
129 | | - Ok(false) => (), |
130 | | - Err(e) => { |
131 | | - bail!("Error: {}", e); |
132 | | - } |
133 | | - }; |
134 | | - |
135 | | - let mut groups: Vec<Box<dyn CheckGroup>> = vec![ |
136 | | - Box::new(SystemChecks::new(host_executor.clone())), |
137 | | - Box::new(PVHChecks::new(host_executor.clone())), |
138 | | - Box::new(KernelChecks::new(host_executor.clone())), |
139 | | - Box::new(IOMMUChecks::new(host_executor.clone())), |
140 | | - Box::new(NUMAChecks::new(host_executor.clone())), |
141 | | - ]; |
142 | | - |
143 | | - if record_hostinfo { |
144 | | - println!( |
145 | | - "Collecting information about the current host as part of locally-generated preinstall report." |
146 | | - ); |
147 | | - println!("The information collected will remain on this host."); |
148 | | - groups.push(Box::new(prerecorder::new(host_executor.clone()))); |
149 | | - } |
150 | | - |
151 | | - if byo_kernel { |
152 | | - groups.push(Box::new(BYOKernelChecks::new(host_executor.clone()))); |
153 | | - } |
154 | | - |
155 | | - // If only-checks is specified, only include checks that match the provided ID. |
156 | | - if !only_checks.is_empty() { |
157 | | - let valid_ids: HashSet<_> = groups.iter().map(|g| g.id().to_string()).collect(); |
158 | | - only_checks.iter().for_each(|id| { |
159 | | - if !valid_ids.contains(id) { |
160 | | - println!("{} '{}'", style("Unknown Check:").yellow(), style(id).red()); |
161 | | - } |
162 | | - }); |
163 | | - groups.retain(|group| only_checks.contains(&group.id().to_string())); |
164 | | - } |
165 | | - |
166 | | - groups.sort_by_key(|g| g.category()); |
167 | | - |
168 | | - let mut required_groups_result = Passed; |
169 | | - let mut all_groups_result = Passed; |
170 | | - |
171 | | - let hostname = host_executor |
172 | | - .spawn_in_host_ns(async { std::fs::read_to_string("/etc/hostname").unwrap() }) |
173 | | - .await?; |
174 | | - |
175 | | - let base_dir = if let Some(dir) = report_dir { |
176 | | - PathBuf::from(dir) |
177 | | - } else { |
178 | | - env::temp_dir() |
179 | | - }; |
180 | | - |
181 | | - let base_path = create_base_path(base_dir, hostname.trim(), "preinstall") |
182 | | - .map_err(|e| anyhow!("failed to create bundle base path: {e}"))?; |
183 | | - // Run each check group |
184 | | - for group in groups { |
185 | | - println!( |
186 | | - "{} {} [{}] - {}", |
187 | | - style("Running Group").cyan(), |
188 | | - style(group.name()).cyan().bold(), |
189 | | - style(group.category()).white().bold(), |
190 | | - group.description() |
191 | | - ); |
192 | | - |
193 | | - let check_group_result = group.run().await; |
194 | | - |
195 | | - check_group_result.log_individual_checks(); |
196 | | - |
197 | | - check_group_result.log_group(group.category()); |
198 | | - |
199 | | - // Set final result to Failed if we failed and aren't already in an Errored state |
200 | | - // However, do not allow Optional groups to count towards Errored or Failed state. |
201 | | - if matches!(check_group_result.result, Failed(_)) { |
202 | | - if matches!(group.category(), CheckGroupCategory::Required) |
203 | | - && !matches!(required_groups_result, Errored(_)) |
204 | | - { |
205 | | - required_groups_result = Failed(String::from("group failed")); |
206 | | - } else if !matches!(all_groups_result, Errored(_)) { |
207 | | - all_groups_result = Failed(String::from("group failed")); |
208 | | - } |
209 | | - } |
210 | | - |
211 | | - if matches!(check_group_result.result, Errored(_)) { |
212 | | - if matches!(group.category(), CheckGroupCategory::Required) { |
213 | | - required_groups_result = Errored(String::from("group errored")); |
214 | | - } else { |
215 | | - all_groups_result = Errored(String::from("group errored")); |
216 | | - } |
217 | | - } |
218 | | - |
219 | | - write_group_report(group, &check_group_result, &base_path)?; |
220 | | - } |
221 | | - |
222 | | - create_gzip_from(base_path, host_executor.clone()).await?; |
223 | | - |
224 | | - match required_groups_result { |
225 | | - Errored(_) | Failed(_) => bail!("Required preinstall checks did not pass"), |
226 | | - _ => Ok(()), |
227 | | - } |
228 | 84 | } |
229 | 85 | Commands::Postinstall { |
230 | 86 | record_hostinfo, |
231 | 87 | only_checks, |
232 | 88 | report_dir, |
233 | | - } => { |
234 | | - // If we are in a privileged container running in the host pid namespace, |
235 | | - // this creates a tokio thread pool that runs stuff outside of the container context, |
236 | | - // directly on the host. |
237 | | - // If we are in a regular old `sudo`'d binary running naked on the host, |
238 | | - // this is effectively a silent no-op. |
239 | | - let host_executor = HostNamespaceExecutor::new(); |
240 | | - |
241 | | - // See if we are already booted under Edera. If so, error out and suggest `postinstall` |
242 | | - // as the command to run. |
243 | | - match host_executor |
244 | | - .spawn_in_host_ns(async { |
245 | | - if !Path::new("/var/lib/edera/protect/.install-completed").exists() { |
246 | | - return false; |
247 | | - } |
248 | | - let xen = Path::new("/sys/hypervisor/type"); |
249 | | - xen.exists() && fs::read_to_string(xen).unwrap_or_default().trim() == "xen" |
250 | | - }) |
251 | | - .await |
252 | | - { |
253 | | - // TODO(bml) later we may add a `postinstall` command, |
254 | | - // but for now all we have is `preinstall` and running it under an active Edera boot |
255 | | - // is not supported or useful. |
256 | | - Ok(true) => {} |
257 | | - Ok(false) => { |
258 | | - println!( |
259 | | - "{}", |
260 | | - style("Edera not installed. Run `edera-check preinstall` instead.") |
261 | | - .red() |
262 | | - .bold() |
263 | | - ); |
264 | | - process::exit(1); |
265 | | - } |
266 | | - Err(e) => { |
267 | | - bail!("Error: {}", e); |
268 | | - } |
269 | | - }; |
270 | | - |
271 | | - let mut groups: Vec<Box<dyn CheckGroup>> = vec![ |
272 | | - Box::new(GuestTypeChecks::new(host_executor.clone())), |
273 | | - Box::new(PostinstallKernelChecks::new(host_executor.clone())), |
274 | | - Box::new(ServiceChecks::new(host_executor.clone())), |
275 | | - Box::new(KubeChecks::new(host_executor.clone())), |
276 | | - ]; |
277 | | - |
278 | | - if record_hostinfo { |
279 | | - println!( |
280 | | - "Collecting information about the current host as part of locally-generated preinstall report." |
281 | | - ); |
282 | | - println!("The information collected will remain on this host."); |
283 | | - groups.push(Box::new(postrecorder::new(host_executor.clone()))); |
284 | | - } |
285 | | - |
286 | | - // If only-checks is specified, only include checks that match the provided ID. |
287 | | - if !only_checks.is_empty() { |
288 | | - let valid_ids: HashSet<_> = groups.iter().map(|g| g.id().to_string()).collect(); |
289 | | - only_checks.iter().for_each(|id| { |
290 | | - if !valid_ids.contains(id) { |
291 | | - println!("{} '{}'", style("Unknown Check:").yellow(), style(id).red()); |
292 | | - } |
293 | | - }); |
294 | | - groups.retain(|group| only_checks.contains(&group.id().to_string())); |
295 | | - } |
296 | | - |
297 | | - groups.sort_by_key(|g| g.category()); |
298 | | - |
299 | | - let mut required_groups_result = Passed; |
300 | | - let mut all_groups_result = Passed; |
301 | | - |
302 | | - let hostname = host_executor |
303 | | - .spawn_in_host_ns(async { std::fs::read_to_string("/etc/hostname").unwrap() }) |
304 | | - .await?; |
305 | | - |
306 | | - let base_dir = if let Some(dir) = report_dir { |
307 | | - PathBuf::from(dir) |
308 | | - } else { |
309 | | - env::temp_dir() |
310 | | - }; |
311 | | - |
312 | | - let base_path = create_base_path(base_dir, hostname.trim(), "postinstall") |
313 | | - .map_err(|e| anyhow!("failed to create bundle base path: {e}"))?; |
314 | | - // Run each check group |
315 | | - for group in groups { |
316 | | - println!( |
317 | | - "{} {} [{}] - {}", |
318 | | - style("Running Group").cyan(), |
319 | | - style(group.name()).cyan().bold(), |
320 | | - style(group.category()).white().bold(), |
321 | | - group.description() |
322 | | - ); |
323 | | - |
324 | | - let check_group_result = group.run().await; |
325 | | - |
326 | | - check_group_result.log_individual_checks(); |
327 | | - |
328 | | - check_group_result.log_group(group.category()); |
329 | | - |
330 | | - // Set final result to Failed if we failed and aren't already in an Errored state |
331 | | - // However, do not allow Optional groups to count towards Errored or Failed state. |
332 | | - if matches!(check_group_result.result, Failed(_)) { |
333 | | - if matches!(group.category(), CheckGroupCategory::Required) |
334 | | - && !matches!(required_groups_result, Errored(_)) |
335 | | - { |
336 | | - required_groups_result = Failed(String::from("group failed")); |
337 | | - } else if !matches!(all_groups_result, Errored(_)) { |
338 | | - all_groups_result = Failed(String::from("group failed")); |
339 | | - } |
340 | | - } |
341 | | - |
342 | | - if matches!(check_group_result.result, Errored(_)) { |
343 | | - if matches!(group.category(), CheckGroupCategory::Required) { |
344 | | - required_groups_result = Errored(String::from("group errored")); |
345 | | - } else { |
346 | | - all_groups_result = Errored(String::from("group errored")); |
347 | | - } |
348 | | - } |
349 | | - |
350 | | - write_group_report(group, &check_group_result, &base_path)?; |
351 | | - } |
352 | | - |
353 | | - create_gzip_from(base_path, host_executor.clone()).await?; |
354 | | - |
355 | | - match required_groups_result { |
356 | | - Errored(_) | Failed(_) => bail!("Required preinstall checks did not pass"), |
357 | | - _ => Ok(()), |
358 | | - } |
359 | | - } |
| 89 | + } => postinstall_cmd::do_postinstall(record_hostinfo, only_checks, report_dir).await, |
360 | 90 | } |
361 | 91 | } |
362 | 92 |
|
|
0 commit comments