Skip to content

Commit 45adfe0

Browse files
committed
refactor(markdown): extract components and make them display
1 parent 629a208 commit 45adfe0

File tree

7 files changed

+516
-431
lines changed

7 files changed

+516
-431
lines changed

src/app/markdown/markdown_data.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
use crate::domain::scanresult::scan_result::ScanResult;
4+
5+
use super::{
6+
markdown_fixable_package_table::FixablePackageTable,
7+
markdown_policy_evaluated_table::PolicyEvaluatedTable, markdown_summary::MarkdownSummary,
8+
markdown_vulnerability_evaluated_table::VulnerabilityEvaluatedTable,
9+
};
10+
11+
#[derive(Clone, Debug, Default)]
12+
pub struct MarkdownData {
13+
pub summary: MarkdownSummary,
14+
pub fixable_packages: FixablePackageTable,
15+
pub policies: PolicyEvaluatedTable,
16+
pub vulnerabilities: VulnerabilityEvaluatedTable,
17+
}
18+
19+
impl From<ScanResult> for MarkdownData {
20+
fn from(value: ScanResult) -> Self {
21+
Self {
22+
summary: MarkdownSummary::from(&value),
23+
fixable_packages: FixablePackageTable::from(&value),
24+
policies: PolicyEvaluatedTable::from(&value),
25+
vulnerabilities: VulnerabilityEvaluatedTable::from(&value),
26+
}
27+
}
28+
}
29+
30+
impl Display for MarkdownData {
31+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32+
let summary_section = self.summary.to_string();
33+
let fixable_packages_section = self.fixable_packages.to_string();
34+
let policy_evaluation_section = self.policies.to_string();
35+
let vulnerability_detail_section = self.vulnerabilities.to_string();
36+
37+
write!(
38+
f,
39+
"## Sysdig Scan Result\n{}\n{}\n{}\n{}",
40+
summary_section,
41+
fixable_packages_section,
42+
policy_evaluation_section,
43+
vulnerability_detail_section
44+
)
45+
}
46+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
use markdown_table::{Heading, HeadingAlignment, MarkdownTable};
4+
5+
use crate::domain::scanresult::{scan_result::ScanResult, severity::Severity};
6+
7+
#[derive(Clone, Debug, Default)]
8+
pub struct FixablePackage {
9+
pub name: String,
10+
pub package_type: String,
11+
pub version: String,
12+
pub suggested_fix: Option<String>,
13+
pub vulnerabilities: FixablePackageVulnerabilities,
14+
pub exploits: u32,
15+
}
16+
17+
#[derive(Clone, Debug, Default)]
18+
pub struct FixablePackageVulnerabilities {
19+
pub critical: u32,
20+
pub high: u32,
21+
pub medium: u32,
22+
pub low: u32,
23+
pub negligible: u32,
24+
}
25+
26+
#[derive(Clone, Debug, Default)]
27+
pub struct FixablePackageTable(pub Vec<FixablePackage>);
28+
29+
impl From<&ScanResult> for FixablePackageTable {
30+
fn from(value: &ScanResult) -> Self {
31+
FixablePackageTable(
32+
value
33+
.packages()
34+
.into_iter()
35+
.filter(|p| p.vulnerabilities().iter().any(|v| v.fixable()))
36+
.map(|p| {
37+
let mut vulns = FixablePackageVulnerabilities::default();
38+
let mut exploits = 0;
39+
for v in p.vulnerabilities() {
40+
if v.exploitable() {
41+
exploits += 1;
42+
}
43+
match v.severity() {
44+
Severity::Critical => vulns.critical += 1,
45+
Severity::High => vulns.high += 1,
46+
Severity::Medium => vulns.medium += 1,
47+
Severity::Low => vulns.low += 1,
48+
Severity::Negligible => vulns.negligible += 1,
49+
Severity::Unknown => {}
50+
}
51+
}
52+
53+
FixablePackage {
54+
name: p.name().to_string(),
55+
package_type: p.package_type().to_string(),
56+
version: p.version().to_string(),
57+
suggested_fix: p
58+
.vulnerabilities()
59+
.iter()
60+
.find_map(|v| v.fix_version().map(|s| s.to_string())),
61+
vulnerabilities: vulns,
62+
exploits,
63+
}
64+
})
65+
.collect(),
66+
)
67+
}
68+
}
69+
70+
impl Display for FixablePackageTable {
71+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72+
if self.0.is_empty() {
73+
return f.write_str("");
74+
}
75+
76+
let headers = vec![
77+
Heading::new("PACKAGE".to_string(), Some(HeadingAlignment::Left)),
78+
Heading::new("TYPE".to_string(), Some(HeadingAlignment::Center)),
79+
Heading::new("VERSION".to_string(), Some(HeadingAlignment::Left)),
80+
Heading::new("SUGGESTED FIX".to_string(), Some(HeadingAlignment::Left)),
81+
Heading::new("CRITICAL".to_string(), Some(HeadingAlignment::Center)),
82+
Heading::new("HIGH".to_string(), Some(HeadingAlignment::Center)),
83+
Heading::new("MEDIUM".to_string(), Some(HeadingAlignment::Center)),
84+
Heading::new("LOW".to_string(), Some(HeadingAlignment::Center)),
85+
Heading::new("NEGLIGIBLE".to_string(), Some(HeadingAlignment::Center)),
86+
Heading::new("EXPLOIT".to_string(), Some(HeadingAlignment::Center)),
87+
];
88+
89+
let data = self
90+
.0
91+
.iter()
92+
.map(|p| {
93+
vec![
94+
p.name.clone(),
95+
p.package_type.clone(),
96+
p.version.clone(),
97+
p.suggested_fix.clone().unwrap_or_default(),
98+
if p.vulnerabilities.critical > 0 {
99+
p.vulnerabilities.critical.to_string()
100+
} else {
101+
"-".to_string()
102+
},
103+
if p.vulnerabilities.high > 0 {
104+
p.vulnerabilities.high.to_string()
105+
} else {
106+
"-".to_string()
107+
},
108+
if p.vulnerabilities.medium > 0 {
109+
p.vulnerabilities.medium.to_string()
110+
} else {
111+
"-".to_string()
112+
},
113+
if p.vulnerabilities.low > 0 {
114+
p.vulnerabilities.low.to_string()
115+
} else {
116+
"-".to_string()
117+
},
118+
if p.vulnerabilities.negligible > 0 {
119+
p.vulnerabilities.negligible.to_string()
120+
} else {
121+
"-".to_string()
122+
},
123+
if p.exploits > 0 {
124+
p.exploits.to_string()
125+
} else {
126+
"-".to_string()
127+
},
128+
]
129+
})
130+
.collect();
131+
132+
let mut table = MarkdownTable::new(data);
133+
table.with_headings(headers);
134+
135+
let format = format!(
136+
"\n### Fixable Packages\n{}",
137+
table.as_markdown().unwrap_or_default()
138+
);
139+
140+
f.write_str(&format)
141+
}
142+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
use itertools::Itertools;
4+
use markdown_table::{Heading, HeadingAlignment, MarkdownTable};
5+
6+
use crate::domain::scanresult::scan_result::ScanResult;
7+
8+
#[derive(Clone, Debug, Default)]
9+
pub struct PolicyEvaluated {
10+
pub name: String,
11+
pub passed: bool,
12+
pub failures: u32,
13+
pub risks_accepted: u32,
14+
}
15+
16+
#[derive(Clone, Debug, Default)]
17+
pub struct PolicyEvaluatedTable(pub Vec<PolicyEvaluated>);
18+
19+
impl Display for PolicyEvaluatedTable {
20+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
21+
if self.0.is_empty() {
22+
return f.write_str("");
23+
}
24+
25+
let headers = vec![
26+
Heading::new("POLICY".to_string(), Some(HeadingAlignment::Left)),
27+
Heading::new("STATUS".to_string(), Some(HeadingAlignment::Center)),
28+
Heading::new("FAILURES".to_string(), Some(HeadingAlignment::Center)),
29+
Heading::new("RISKS ACCEPTED".to_string(), Some(HeadingAlignment::Center)),
30+
];
31+
32+
let data = self
33+
.0
34+
.iter()
35+
.map(|p| {
36+
vec![
37+
p.name.clone(),
38+
if p.passed { "✅" } else { "❌" }.to_string(),
39+
p.failures.to_string(),
40+
p.risks_accepted.to_string(),
41+
]
42+
})
43+
.collect();
44+
45+
let mut table = MarkdownTable::new(data);
46+
table.with_headings(headers);
47+
48+
let format = format!(
49+
"\n### Policy Evaluation\n\n{}",
50+
table.as_markdown().unwrap_or_default()
51+
);
52+
53+
f.write_str(&format)
54+
}
55+
}
56+
57+
impl From<&ScanResult> for PolicyEvaluatedTable {
58+
fn from(value: &ScanResult) -> Self {
59+
PolicyEvaluatedTable(
60+
value
61+
.policies()
62+
.iter()
63+
.map(|p| PolicyEvaluated {
64+
name: p.name().to_string(),
65+
passed: p.evaluation_result().is_passed(),
66+
failures: p.bundles().iter().map(|b| b.rules().len()).sum::<usize>() as u32,
67+
risks_accepted: 0, // FIXME(fede): Cannot determine this from the current data model
68+
})
69+
.sorted_by(|a, b| b.failures.cmp(&a.failures))
70+
.sorted_by_key(|p| p.passed)
71+
.collect(),
72+
)
73+
}
74+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
use crate::domain::scanresult::scan_result::ScanResult;
4+
5+
use super::markdown_summary_table::MarkdownSummaryTable;
6+
7+
#[derive(Clone, Debug, Default)]
8+
pub struct MarkdownSummary {
9+
pub pull_string: String,
10+
pub image_id: String,
11+
pub digest: String,
12+
pub base_os: String,
13+
pub total_vulns_found: MarkdownSummaryTable,
14+
}
15+
16+
impl From<&ScanResult> for MarkdownSummary {
17+
fn from(value: &ScanResult) -> Self {
18+
MarkdownSummary {
19+
pull_string: value.metadata().pull_string().to_string(),
20+
image_id: value.metadata().image_id().to_string(),
21+
digest: value.metadata().digest().unwrap_or("").to_string(),
22+
base_os: value.metadata().base_os().name().to_string(),
23+
total_vulns_found: MarkdownSummaryTable::from(value),
24+
}
25+
}
26+
}
27+
28+
impl Display for MarkdownSummary {
29+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30+
let format = format!(
31+
"### Summary\n* **PullString**: {}\n* **ImageID**: `{}`\n* **Digest**: `{}`\n* **BaseOS**: {}\n\n{}",
32+
&self.pull_string, &self.image_id, &self.digest, &self.base_os, &self.total_vulns_found
33+
);
34+
35+
f.write_str(&format)
36+
}
37+
}

0 commit comments

Comments
 (0)