use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use crate::analyzer::helmlint::types::Severity;
#[derive(Debug, Clone)]
pub struct HelmlintConfig {
pub ignored_rules: HashSet<String>,
pub severity_overrides: HashMap<String, Severity>,
pub failure_threshold: Severity,
pub disable_ignore_pragma: bool,
pub no_fail: bool,
pub k8s_version: Option<String>,
pub values_schema_path: Option<PathBuf>,
pub strict: bool,
pub fixable_only: bool,
pub exclude_patterns: Vec<String>,
}
impl Default for HelmlintConfig {
fn default() -> Self {
Self {
ignored_rules: HashSet::new(),
severity_overrides: HashMap::new(),
failure_threshold: Severity::Warning,
disable_ignore_pragma: false,
no_fail: false,
k8s_version: None,
values_schema_path: None,
strict: false,
fixable_only: false,
exclude_patterns: Vec::new(),
}
}
}
impl HelmlintConfig {
pub fn new() -> Self {
Self::default()
}
pub fn ignore(mut self, rule: impl Into<String>) -> Self {
self.ignored_rules.insert(rule.into());
self
}
pub fn ignore_all(mut self, rules: impl IntoIterator<Item = impl Into<String>>) -> Self {
for rule in rules {
self.ignored_rules.insert(rule.into());
}
self
}
pub fn with_severity(mut self, rule: impl Into<String>, severity: Severity) -> Self {
self.severity_overrides.insert(rule.into(), severity);
self
}
pub fn with_threshold(mut self, threshold: Severity) -> Self {
self.failure_threshold = threshold;
self
}
pub fn with_k8s_version(mut self, version: impl Into<String>) -> Self {
self.k8s_version = Some(version.into());
self
}
pub fn with_values_schema(mut self, path: impl Into<PathBuf>) -> Self {
self.values_schema_path = Some(path.into());
self
}
pub fn with_strict(mut self, strict: bool) -> Self {
self.strict = strict;
self
}
pub fn is_rule_ignored(&self, code: &str) -> bool {
self.ignored_rules.contains(code)
}
pub fn effective_severity(&self, code: &str, default: Severity) -> Severity {
if let Some(&override_severity) = self.severity_overrides.get(code) {
override_severity
} else if self.strict && default == Severity::Warning {
Severity::Error
} else {
default
}
}
pub fn should_report(&self, severity: Severity) -> bool {
severity >= self.failure_threshold
}
pub fn is_excluded(&self, path: &str) -> bool {
for pattern in &self.exclude_patterns {
if path.contains(pattern) {
return true;
}
if pattern.contains('*') {
let parts: Vec<&str> = pattern.split('*').collect();
let mut remaining = path;
let mut matched = true;
for (i, part) in parts.iter().enumerate() {
if part.is_empty() {
continue;
}
if i == 0 {
if !remaining.starts_with(part) {
matched = false;
break;
}
remaining = &remaining[part.len()..];
} else if i == parts.len() - 1 {
if !remaining.ends_with(part) {
matched = false;
break;
}
} else if let Some(pos) = remaining.find(part) {
remaining = &remaining[pos + part.len()..];
} else {
matched = false;
break;
}
}
if matched {
return true;
}
}
}
false
}
pub fn parse_k8s_version(&self) -> Option<(u32, u32)> {
self.k8s_version.as_ref().and_then(|v| {
let v = v.trim_start_matches('v');
let parts: Vec<&str> = v.split('.').collect();
if parts.len() >= 2 {
let major = parts[0].parse().ok()?;
let minor = parts[1].parse().ok()?;
Some((major, minor))
} else {
None
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = HelmlintConfig::default();
assert!(config.ignored_rules.is_empty());
assert!(config.severity_overrides.is_empty());
assert_eq!(config.failure_threshold, Severity::Warning);
assert!(!config.strict);
}
#[test]
fn test_ignore_rule() {
let config = HelmlintConfig::default().ignore("HL1001");
assert!(config.is_rule_ignored("HL1001"));
assert!(!config.is_rule_ignored("HL1002"));
}
#[test]
fn test_severity_override() {
let config = HelmlintConfig::default().with_severity("HL1001", Severity::Error);
assert_eq!(
config.effective_severity("HL1001", Severity::Warning),
Severity::Error
);
assert_eq!(
config.effective_severity("HL1002", Severity::Warning),
Severity::Warning
);
}
#[test]
fn test_strict_mode() {
let config = HelmlintConfig::default().with_strict(true);
assert_eq!(
config.effective_severity("HL1001", Severity::Warning),
Severity::Error
);
assert_eq!(
config.effective_severity("HL1001", Severity::Info),
Severity::Info
);
}
#[test]
fn test_k8s_version_parsing() {
let config = HelmlintConfig::default().with_k8s_version("v1.28");
assert_eq!(config.parse_k8s_version(), Some((1, 28)));
let config = HelmlintConfig::default().with_k8s_version("1.25.0");
assert_eq!(config.parse_k8s_version(), Some((1, 25)));
}
#[test]
fn test_exclusion() {
let mut config = HelmlintConfig::default();
config.exclude_patterns = vec!["test".to_string(), "*.bak".to_string()];
assert!(config.is_excluded("templates/test.yaml"));
assert!(config.is_excluded("backup.bak"));
assert!(!config.is_excluded("templates/deployment.yaml"));
}
}