syncable_cli/analyzer/kubelint/formatter/
sarif.rs1use crate::analyzer::kubelint::lint::LintResult;
7use serde::Serialize;
8
9pub fn format(result: &LintResult) -> String {
11 let output = SarifOutput::from(result);
12 serde_json::to_string_pretty(&output).unwrap_or_else(|_| "{}".to_string())
13}
14
15#[derive(Serialize)]
16struct SarifOutput {
17 #[serde(rename = "$schema")]
18 schema: String,
19 version: String,
20 runs: Vec<SarifRun>,
21}
22
23#[derive(Serialize)]
24struct SarifRun {
25 tool: SarifTool,
26 results: Vec<SarifResult>,
27}
28
29#[derive(Serialize)]
30struct SarifTool {
31 driver: SarifDriver,
32}
33
34#[derive(Serialize)]
35struct SarifDriver {
36 name: String,
37 version: String,
38 #[serde(rename = "informationUri")]
39 information_uri: String,
40 rules: Vec<SarifRule>,
41}
42
43#[derive(Serialize)]
44struct SarifRule {
45 id: String,
46 name: String,
47 #[serde(rename = "shortDescription")]
48 short_description: SarifMessage,
49 #[serde(rename = "defaultConfiguration")]
50 default_configuration: SarifConfiguration,
51}
52
53#[derive(Serialize)]
54struct SarifConfiguration {
55 level: String,
56}
57
58#[derive(Serialize)]
59struct SarifResult {
60 #[serde(rename = "ruleId")]
61 rule_id: String,
62 level: String,
63 message: SarifMessage,
64 locations: Vec<SarifLocation>,
65}
66
67#[derive(Serialize)]
68struct SarifMessage {
69 text: String,
70}
71
72#[derive(Serialize)]
73struct SarifLocation {
74 #[serde(rename = "physicalLocation")]
75 physical_location: SarifPhysicalLocation,
76}
77
78#[derive(Serialize)]
79struct SarifPhysicalLocation {
80 #[serde(rename = "artifactLocation")]
81 artifact_location: SarifArtifactLocation,
82 region: Option<SarifRegion>,
83}
84
85#[derive(Serialize)]
86struct SarifArtifactLocation {
87 uri: String,
88}
89
90#[derive(Serialize)]
91struct SarifRegion {
92 #[serde(rename = "startLine")]
93 start_line: u32,
94}
95
96impl From<&LintResult> for SarifOutput {
97 fn from(result: &LintResult) -> Self {
98 let mut rules: Vec<SarifRule> = Vec::new();
100 let mut seen_rules = std::collections::HashSet::new();
101
102 for failure in &result.failures {
103 let rule_id = failure.code.to_string();
104 if !seen_rules.contains(&rule_id) {
105 seen_rules.insert(rule_id.clone());
106 rules.push(SarifRule {
107 id: rule_id.clone(),
108 name: rule_id.clone(),
109 short_description: SarifMessage {
110 text: failure.message.clone(),
111 },
112 default_configuration: SarifConfiguration {
113 level: severity_to_sarif_level(failure.severity),
114 },
115 });
116 }
117 }
118
119 let results: Vec<SarifResult> = result
120 .failures
121 .iter()
122 .map(|f| SarifResult {
123 rule_id: f.code.to_string(),
124 level: severity_to_sarif_level(f.severity),
125 message: SarifMessage {
126 text: format!(
127 "{} ({}/{}): {}",
128 f.code, f.object_kind, f.object_name, f.message
129 ),
130 },
131 locations: vec![SarifLocation {
132 physical_location: SarifPhysicalLocation {
133 artifact_location: SarifArtifactLocation {
134 uri: f.file_path.display().to_string(),
135 },
136 region: f.line.map(|l| SarifRegion { start_line: l }),
137 },
138 }],
139 })
140 .collect();
141
142 Self {
143 schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json".to_string(),
144 version: "2.1.0".to_string(),
145 runs: vec![SarifRun {
146 tool: SarifTool {
147 driver: SarifDriver {
148 name: "kubelint-rs".to_string(),
149 version: env!("CARGO_PKG_VERSION").to_string(),
150 information_uri: "https://github.com/stackrox/kube-linter".to_string(),
151 rules,
152 },
153 },
154 results,
155 }],
156 }
157 }
158}
159
160fn severity_to_sarif_level(severity: crate::analyzer::kubelint::types::Severity) -> String {
161 match severity {
162 crate::analyzer::kubelint::types::Severity::Error => "error".to_string(),
163 crate::analyzer::kubelint::types::Severity::Warning => "warning".to_string(),
164 crate::analyzer::kubelint::types::Severity::Info => "note".to_string(),
165 }
166}