syncable_cli/analyzer/helmlint/rules/
mod.rs

1//! Rule system for helmlint.
2//!
3//! Provides the infrastructure for defining and running Helm chart linting rules.
4//!
5//! # Rule Categories
6//!
7//! - **HL1xxx**: Chart structure rules (Chart.yaml, file structure)
8//! - **HL2xxx**: Values validation rules (values.yaml)
9//! - **HL3xxx**: Template syntax rules (Go templates)
10//! - **HL4xxx**: Security rules (container security)
11//! - **HL5xxx**: Best practice rules (K8s best practices)
12
13pub mod hl1xxx;
14pub mod hl2xxx;
15pub mod hl3xxx;
16pub mod hl4xxx;
17pub mod hl5xxx;
18
19use std::collections::HashSet;
20use std::path::Path;
21
22use crate::analyzer::helmlint::parser::chart::ChartMetadata;
23use crate::analyzer::helmlint::parser::helpers::ParsedHelpers;
24use crate::analyzer::helmlint::parser::template::ParsedTemplate;
25use crate::analyzer::helmlint::parser::values::ValuesFile;
26use crate::analyzer::helmlint::types::{CheckFailure, Severity};
27
28/// Context for running lint rules.
29#[derive(Debug)]
30pub struct LintContext<'a> {
31    /// Path to the chart root directory.
32    pub chart_path: &'a Path,
33    /// Parsed Chart.yaml (if available).
34    pub chart_metadata: Option<&'a ChartMetadata>,
35    /// Parsed values.yaml (if available).
36    pub values: Option<&'a ValuesFile>,
37    /// Parsed helper templates.
38    pub helpers: Option<&'a ParsedHelpers>,
39    /// All parsed templates.
40    pub templates: &'a [ParsedTemplate],
41    /// All files in the chart.
42    pub files: &'a HashSet<String>,
43    /// All value references found in templates.
44    pub template_value_refs: HashSet<String>,
45}
46
47impl<'a> LintContext<'a> {
48    /// Create a new lint context.
49    pub fn new(
50        chart_path: &'a Path,
51        chart_metadata: Option<&'a ChartMetadata>,
52        values: Option<&'a ValuesFile>,
53        helpers: Option<&'a ParsedHelpers>,
54        templates: &'a [ParsedTemplate],
55        files: &'a HashSet<String>,
56    ) -> Self {
57        // Collect all value references from templates
58        let mut template_value_refs = HashSet::new();
59        for template in templates {
60            for var in &template.variables_used {
61                if let Some(path) = var.strip_prefix(".Values.") {
62                    template_value_refs.insert(path.to_string());
63                }
64            }
65        }
66
67        Self {
68            chart_path,
69            chart_metadata,
70            values,
71            helpers,
72            templates,
73            files,
74            template_value_refs,
75        }
76    }
77
78    /// Check if a file exists in the chart.
79    pub fn has_file(&self, name: &str) -> bool {
80        self.files.contains(name) || self.files.iter().any(|f| f.ends_with(name))
81    }
82
83    /// Check if a helper is defined.
84    pub fn has_helper(&self, name: &str) -> bool {
85        self.helpers.map(|h| h.has_helper(name)).unwrap_or(false)
86    }
87
88    /// Get all defined helper names.
89    pub fn helper_names(&self) -> Vec<&str> {
90        self.helpers
91            .map(|h| h.names().collect())
92            .unwrap_or_default()
93    }
94
95    /// Get all template references (from include/template calls).
96    pub fn template_references(&self) -> HashSet<&str> {
97        let mut refs = HashSet::new();
98        for template in self.templates {
99            for name in &template.referenced_templates {
100                refs.insert(name.as_str());
101            }
102        }
103        refs
104    }
105}
106
107/// A lint rule that can check Helm charts.
108pub trait Rule: Send + Sync {
109    /// Get the rule code (e.g., "HL1001").
110    fn code(&self) -> &'static str;
111
112    /// Get the default severity.
113    fn severity(&self) -> Severity;
114
115    /// Get the rule name.
116    fn name(&self) -> &'static str;
117
118    /// Get the rule description.
119    fn description(&self) -> &'static str;
120
121    /// Check if this rule can be auto-fixed.
122    fn is_fixable(&self) -> bool {
123        false
124    }
125
126    /// Run the rule and return any violations.
127    fn check(&self, ctx: &LintContext) -> Vec<CheckFailure>;
128}
129
130/// Get all available rules.
131pub fn all_rules() -> Vec<Box<dyn Rule>> {
132    let mut rules: Vec<Box<dyn Rule>> = Vec::new();
133
134    // HL1xxx - Chart Structure Rules
135    rules.extend(hl1xxx::rules());
136
137    // HL2xxx - Values Validation Rules
138    rules.extend(hl2xxx::rules());
139
140    // HL3xxx - Template Syntax Rules
141    rules.extend(hl3xxx::rules());
142
143    // HL4xxx - Security Rules
144    rules.extend(hl4xxx::rules());
145
146    // HL5xxx - Best Practice Rules
147    rules.extend(hl5xxx::rules());
148
149    rules
150}
151
152/// Get a rule by code.
153pub fn get_rule(code: &str) -> Option<Box<dyn Rule>> {
154    all_rules().into_iter().find(|r| r.code() == code)
155}
156
157/// List all rule codes.
158pub fn list_rule_codes() -> Vec<&'static str> {
159    vec![
160        // HL1xxx
161        "HL1001", "HL1002", "HL1003", "HL1004", "HL1005", "HL1006", "HL1007", "HL1008", "HL1009",
162        "HL1010", "HL1011", "HL1012", "HL1013", "HL1014", "HL1015", "HL1016", "HL1017",
163        // HL2xxx
164        "HL2001", "HL2002", "HL2003", "HL2004", "HL2005", "HL2006", "HL2007", "HL2008", "HL2009",
165        // HL3xxx
166        "HL3001", "HL3002", "HL3003", "HL3004", "HL3005", "HL3006", "HL3007", "HL3008", "HL3009",
167        "HL3010", "HL3011", // HL4xxx
168        "HL4001", "HL4002", "HL4003", "HL4004", "HL4005", "HL4006", "HL4007", "HL4008", "HL4009",
169        "HL4010", "HL4011", "HL4012", // HL5xxx
170        "HL5001", "HL5002", "HL5003", "HL5004", "HL5005", "HL5006",
171    ]
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_all_rules_returns_rules() {
180        let rules = all_rules();
181        assert!(!rules.is_empty());
182    }
183
184    #[test]
185    fn test_rule_codes_unique() {
186        let rules = all_rules();
187        let mut codes = HashSet::new();
188        for rule in rules {
189            let code = rule.code();
190            assert!(codes.insert(code), "Duplicate rule code: {}", code);
191        }
192    }
193}