Skip to content

Commit fb3e5d1

Browse files
committed
feat: add report bundle
Signed-off-by: James Petersen <jpetersenames@gmail.com>
1 parent dde39fb commit fb3e5d1

8 files changed

Lines changed: 148 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ edition = "2024"
55

66
[dependencies]
77
anyhow = "1.0.99"
8+
chrono = "0.4.42"
89
env_logger = "0.11.8"
10+
flate2 = "1.1.2"
911
log = "0.4.28"
1012
procfs = "0.18.0"
1113
sysinfo = "0.36.1"
14+
tar = "0.4.44"

scripts/pvh.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/sh
22
set -eu
33

4+
echo "EDERA_PREFLIGHT_CHECK_NAME=Can PVH Be Enabled"
5+
46
# prerequisites: msr-tools (rdmsr), and the msr kernel module
57
modprobe msr 2>/dev/null || true
68

src/hardware.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use super::{
33
CheckResultValue::{Errored, Failed, Passed},
44
};
55

6-
use log::debug;
76
use std::process::Command;
87

98
const GROUP_IDENTIFIER: &str = "HardwareChecks";
@@ -50,11 +49,7 @@ impl HardwareChecks {
5049

5150
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
5251

53-
for line in stdout.lines() {
54-
debug!("{}", line);
55-
}
56-
57-
CheckResult::new(&name, Passed)
52+
CheckResult::new_with_output(&name, Passed, Some(stdout))
5853
}
5954

6055
fn record_lspci(&self) -> CheckResult {

src/kernel/mod.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ impl KernelChecks {
114114
// Search loaded modules
115115
let modules = procfs::KernelModules::current();
116116
if let Err(e) = modules {
117-
return CheckResult {
118-
name,
119-
result: Errored(format!("getting kernel modules {e}")),
120-
};
117+
return CheckResult::new(&name, Errored(format!("getting kernel modules {e}")));
121118
}
122119
let modules = modules.unwrap();
123120

src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,22 @@ pub struct CheckResult {
3333

3434
/// result is the final result of an individual check
3535
pub result: CheckResultValue,
36+
37+
/// output_to_record is an optional field used to return output that should be recorded into an
38+
/// information bundle
39+
pub output_to_record: Option<String>,
3640
}
3741

3842
impl CheckResult {
3943
pub fn new(name: &str, result: CheckResultValue) -> Self {
44+
Self::new_with_output(name, result, None)
45+
}
46+
47+
pub fn new_with_output(name: &str, result: CheckResultValue, output_to_record: Option<String>) -> Self {
4048
Self {
4149
name: name.to_string(),
4250
result,
51+
output_to_record,
4352
}
4453
}
4554
}

src/main.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,86 @@
11
use preflight::{
2-
CheckGroup,
2+
CheckGroup, CheckGroupResult,
33
CheckResultValue::{Errored, Failed, Passed},
4+
hardware::HardwareChecks,
45
kernel::KernelChecks,
56
script::ScriptChecks,
67
system::SystemChecks,
7-
hardware::HardwareChecks,
88
};
99

10-
use anyhow::{Result, anyhow, bail};
10+
use anyhow::{Context, Result, anyhow, bail};
11+
use chrono::Utc;
12+
use flate2::Compression;
13+
use flate2::write::GzEncoder;
1114
use log::info;
1215
use std::env;
16+
use std::fs;
17+
use std::fs::File;
1318
use std::os::unix;
19+
use std::path::{Path, PathBuf};
1420

1521
// Skip certain groups. List is separated by ;
1622
fn skip_groups() -> Vec<String> {
1723
let skips = env::var("EDERA_PREFLIGHT_SKIP_GROUPS").unwrap_or_default();
1824
skips.split(";").map(|s| s.to_string()).collect()
1925
}
2026

27+
fn create_gzip_from(base_path: PathBuf) -> Result<()> {
28+
let mut archive_path = base_path.clone();
29+
archive_path.set_extension("tar.gz");
30+
let tar_gz = File::create(&archive_path)
31+
.with_context(|| format!("failed to create {}", archive_path.display()))?;
32+
let enc = GzEncoder::new(tar_gz, Compression::default());
33+
let mut tar = tar::Builder::new(enc);
34+
tar.append_dir_all(".", base_path)
35+
.context("failed to append to tar {}")?;
36+
info!("Wrote to: {}", archive_path.to_string_lossy());
37+
Ok(())
38+
}
39+
40+
fn create_base_path() -> Result<PathBuf> {
41+
let now = Utc::now();
42+
43+
let base = env::var("EDERA_PREFLIGHT_REPORT_DIR")
44+
.map(PathBuf::from)
45+
.unwrap_or(env::temp_dir());
46+
47+
let base_path = base.join(format!(
48+
"protect-preflight-bundle-{}",
49+
now.format("%Y%m%d-%H%M%S")
50+
));
51+
fs::create_dir_all(&base_path)
52+
.with_context(|| format!("could not create {}", base_path.display()))?;
53+
info!("Writing all files to {}", base_path.to_string_lossy());
54+
Ok(base_path)
55+
}
56+
57+
fn write_group_report(
58+
group: Box<dyn CheckGroup>,
59+
result: &CheckGroupResult,
60+
path: &Path,
61+
) -> Result<()> {
62+
let path = path.join(group.id());
63+
fs::create_dir_all(&path).with_context(|| format!("could not create {}", path.display()))?;
64+
65+
for check in result.results.iter() {
66+
// Sanitize the name of the check into a flat file. Script Checks default to the path of
67+
// the script as the name so we need to sanitize.
68+
let name = check
69+
.name
70+
.replace(" ", "_")
71+
.replace("/", "_")
72+
.replace(".", "");
73+
74+
let path = path.join(name);
75+
match check.output_to_record.as_ref() {
76+
Some(text) => fs::write(&path, text),
77+
None => fs::write(&path, format!("{}", check.result)),
78+
}
79+
.with_context(|| format!("failed to write to {}", path.display()))?;
80+
}
81+
Ok(())
82+
}
83+
2184
fn main() -> Result<()> {
2285
env_logger::init();
2386

@@ -36,6 +99,9 @@ fn main() -> Result<()> {
3699
.map_err(|e| anyhow!("failed to chroot to {target_dir}: {e}"))?;
37100
}
38101

102+
let base_path =
103+
create_base_path().map_err(|e| anyhow!("failed to create bundle base path: {e}"))?;
104+
39105
// Run each check group
40106
for group in groups {
41107
// Check if we need to explicity skip this group
@@ -61,8 +127,12 @@ fn main() -> Result<()> {
61127
if matches!(check_group_result.result, Errored(_)) {
62128
final_result = Errored(String::from("group errored"));
63129
}
130+
131+
write_group_report(group, &check_group_result, &base_path)?;
64132
}
65133

134+
create_gzip_from(base_path)?;
135+
66136
match final_result {
67137
Errored(_) | Failed(_) => bail!("Preflight checks did not pass"),
68138
_ => Ok(()),

src/script/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl ScriptChecks {
147147
stderr
148148
)),
149149
};
150-
CheckResult::new(&name, result)
150+
CheckResult::new_with_output(&name, result, Some(stdout.to_string()))
151151
}
152152
}
153153

0 commit comments

Comments
 (0)