syncable_cli/analyzer/helmlint/
config.rs1use std::collections::{HashMap, HashSet};
10use std::path::PathBuf;
11
12use crate::analyzer::helmlint::types::Severity;
13
14#[derive(Debug, Clone)]
16pub struct HelmlintConfig {
17 pub ignored_rules: HashSet<String>,
19
20 pub severity_overrides: HashMap<String, Severity>,
22
23 pub failure_threshold: Severity,
25
26 pub disable_ignore_pragma: bool,
28
29 pub no_fail: bool,
31
32 pub k8s_version: Option<String>,
34
35 pub values_schema_path: Option<PathBuf>,
37
38 pub strict: bool,
40
41 pub fixable_only: bool,
43
44 pub exclude_patterns: Vec<String>,
46}
47
48impl Default for HelmlintConfig {
49 fn default() -> Self {
50 Self {
51 ignored_rules: HashSet::new(),
52 severity_overrides: HashMap::new(),
53 failure_threshold: Severity::Warning,
54 disable_ignore_pragma: false,
55 no_fail: false,
56 k8s_version: None,
57 values_schema_path: None,
58 strict: false,
59 fixable_only: false,
60 exclude_patterns: Vec::new(),
61 }
62 }
63}
64
65impl HelmlintConfig {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn ignore(mut self, rule: impl Into<String>) -> Self {
73 self.ignored_rules.insert(rule.into());
74 self
75 }
76
77 pub fn ignore_all(mut self, rules: impl IntoIterator<Item = impl Into<String>>) -> Self {
79 for rule in rules {
80 self.ignored_rules.insert(rule.into());
81 }
82 self
83 }
84
85 pub fn with_severity(mut self, rule: impl Into<String>, severity: Severity) -> Self {
87 self.severity_overrides.insert(rule.into(), severity);
88 self
89 }
90
91 pub fn with_threshold(mut self, threshold: Severity) -> Self {
93 self.failure_threshold = threshold;
94 self
95 }
96
97 pub fn with_k8s_version(mut self, version: impl Into<String>) -> Self {
99 self.k8s_version = Some(version.into());
100 self
101 }
102
103 pub fn with_values_schema(mut self, path: impl Into<PathBuf>) -> Self {
105 self.values_schema_path = Some(path.into());
106 self
107 }
108
109 pub fn with_strict(mut self, strict: bool) -> Self {
111 self.strict = strict;
112 self
113 }
114
115 pub fn is_rule_ignored(&self, code: &str) -> bool {
117 self.ignored_rules.contains(code)
118 }
119
120 pub fn effective_severity(&self, code: &str, default: Severity) -> Severity {
122 if let Some(&override_severity) = self.severity_overrides.get(code) {
123 override_severity
124 } else if self.strict && default == Severity::Warning {
125 Severity::Error
126 } else {
127 default
128 }
129 }
130
131 pub fn should_report(&self, severity: Severity) -> bool {
133 severity >= self.failure_threshold
134 }
135
136 pub fn is_excluded(&self, path: &str) -> bool {
138 for pattern in &self.exclude_patterns {
139 if path.contains(pattern) {
140 return true;
141 }
142 if pattern.contains('*') {
144 let parts: Vec<&str> = pattern.split('*').collect();
145 let mut remaining = path;
146 let mut matched = true;
147 for (i, part) in parts.iter().enumerate() {
148 if part.is_empty() {
149 continue;
150 }
151 if i == 0 {
152 if !remaining.starts_with(part) {
153 matched = false;
154 break;
155 }
156 remaining = &remaining[part.len()..];
157 } else if i == parts.len() - 1 {
158 if !remaining.ends_with(part) {
159 matched = false;
160 break;
161 }
162 } else if let Some(pos) = remaining.find(part) {
163 remaining = &remaining[pos + part.len()..];
164 } else {
165 matched = false;
166 break;
167 }
168 }
169 if matched {
170 return true;
171 }
172 }
173 }
174 false
175 }
176
177 pub fn parse_k8s_version(&self) -> Option<(u32, u32)> {
179 self.k8s_version.as_ref().and_then(|v| {
180 let v = v.trim_start_matches('v');
181 let parts: Vec<&str> = v.split('.').collect();
182 if parts.len() >= 2 {
183 let major = parts[0].parse().ok()?;
184 let minor = parts[1].parse().ok()?;
185 Some((major, minor))
186 } else {
187 None
188 }
189 })
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_default_config() {
199 let config = HelmlintConfig::default();
200 assert!(config.ignored_rules.is_empty());
201 assert!(config.severity_overrides.is_empty());
202 assert_eq!(config.failure_threshold, Severity::Warning);
203 assert!(!config.strict);
204 }
205
206 #[test]
207 fn test_ignore_rule() {
208 let config = HelmlintConfig::default().ignore("HL1001");
209 assert!(config.is_rule_ignored("HL1001"));
210 assert!(!config.is_rule_ignored("HL1002"));
211 }
212
213 #[test]
214 fn test_severity_override() {
215 let config = HelmlintConfig::default().with_severity("HL1001", Severity::Error);
216 assert_eq!(
217 config.effective_severity("HL1001", Severity::Warning),
218 Severity::Error
219 );
220 assert_eq!(
221 config.effective_severity("HL1002", Severity::Warning),
222 Severity::Warning
223 );
224 }
225
226 #[test]
227 fn test_strict_mode() {
228 let config = HelmlintConfig::default().with_strict(true);
229 assert_eq!(
230 config.effective_severity("HL1001", Severity::Warning),
231 Severity::Error
232 );
233 assert_eq!(
234 config.effective_severity("HL1001", Severity::Info),
235 Severity::Info
236 );
237 }
238
239 #[test]
240 fn test_k8s_version_parsing() {
241 let config = HelmlintConfig::default().with_k8s_version("v1.28");
242 assert_eq!(config.parse_k8s_version(), Some((1, 28)));
243
244 let config = HelmlintConfig::default().with_k8s_version("1.25.0");
245 assert_eq!(config.parse_k8s_version(), Some((1, 25)));
246 }
247
248 #[test]
249 fn test_exclusion() {
250 let mut config = HelmlintConfig::default();
251 config.exclude_patterns = vec!["test".to_string(), "*.bak".to_string()];
252
253 assert!(config.is_excluded("templates/test.yaml"));
254 assert!(config.is_excluded("backup.bak"));
255 assert!(!config.is_excluded("templates/deployment.yaml"));
256 }
257}