syncable_cli/analyzer/kubelint/parser/
helm.rs

1//! Helm chart rendering for Kubernetes manifests.
2
3use crate::analyzer::kubelint::context::Object;
4use crate::analyzer::kubelint::parser::yaml;
5use std::path::Path;
6use std::process::Command;
7
8/// Render a Helm chart to Kubernetes objects.
9///
10/// This function shells out to the `helm template` command to render
11/// the chart and then parses the resulting YAML.
12pub fn render_helm_chart(
13    chart_path: &Path,
14    values: Option<&Path>,
15) -> Result<Vec<Object>, HelmError> {
16    // Check if helm binary is available
17    if !is_helm_available() {
18        return Err(HelmError::HelmNotFound);
19    }
20
21    // Build helm template command
22    let mut cmd = Command::new("helm");
23    cmd.arg("template")
24        .arg("release-name") // Use a default release name for linting
25        .arg(chart_path);
26
27    // Add values file if provided
28    if let Some(values_path) = values {
29        cmd.arg("-f").arg(values_path);
30    }
31
32    // Execute helm template
33    let output = cmd
34        .output()
35        .map_err(|e| HelmError::RenderError(e.to_string()))?;
36
37    if !output.status.success() {
38        let stderr = String::from_utf8_lossy(&output.stderr);
39        return Err(HelmError::RenderError(stderr.to_string()));
40    }
41
42    // Parse the rendered YAML
43    let yaml_content = String::from_utf8_lossy(&output.stdout);
44    yaml::parse_yaml_with_path(&yaml_content, chart_path)
45        .map_err(|e| HelmError::RenderError(e.to_string()))
46}
47
48/// Render a Helm chart with custom values.
49pub fn render_helm_chart_with_values(
50    chart_path: &Path,
51    values_files: &[&Path],
52    set_values: &[(&str, &str)],
53) -> Result<Vec<Object>, HelmError> {
54    if !is_helm_available() {
55        return Err(HelmError::HelmNotFound);
56    }
57
58    let mut cmd = Command::new("helm");
59    cmd.arg("template").arg("release-name").arg(chart_path);
60
61    // Add all values files
62    for values_path in values_files {
63        cmd.arg("-f").arg(values_path);
64    }
65
66    // Add --set values
67    for (key, value) in set_values {
68        cmd.arg("--set").arg(format!("{}={}", key, value));
69    }
70
71    let output = cmd
72        .output()
73        .map_err(|e| HelmError::RenderError(e.to_string()))?;
74
75    if !output.status.success() {
76        let stderr = String::from_utf8_lossy(&output.stderr);
77        return Err(HelmError::RenderError(stderr.to_string()));
78    }
79
80    let yaml_content = String::from_utf8_lossy(&output.stdout);
81    yaml::parse_yaml_with_path(&yaml_content, chart_path)
82        .map_err(|e| HelmError::RenderError(e.to_string()))
83}
84
85/// Check if a directory is a Helm chart.
86pub fn is_helm_chart(path: &Path) -> bool {
87    path.join("Chart.yaml").exists() || path.join("Chart.yml").exists()
88}
89
90/// Check if helm binary is available in PATH.
91pub fn is_helm_available() -> bool {
92    Command::new("helm")
93        .arg("version")
94        .arg("--short")
95        .output()
96        .map(|o| o.status.success())
97        .unwrap_or(false)
98}
99
100/// Get Helm version if available.
101pub fn helm_version() -> Option<String> {
102    Command::new("helm")
103        .arg("version")
104        .arg("--short")
105        .output()
106        .ok()
107        .filter(|o| o.status.success())
108        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
109}
110
111/// Helm rendering errors.
112#[derive(Debug, Clone)]
113pub enum HelmError {
114    /// Helm binary not found.
115    HelmNotFound,
116    /// Chart validation error.
117    ChartError(String),
118    /// Rendering error.
119    RenderError(String),
120}
121
122impl std::fmt::Display for HelmError {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        match self {
125            Self::HelmNotFound => write!(f, "helm binary not found in PATH"),
126            Self::ChartError(msg) => write!(f, "Chart error: {}", msg),
127            Self::RenderError(msg) => write!(f, "Render error: {}", msg),
128        }
129    }
130}
131
132impl std::error::Error for HelmError {}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_is_helm_chart_detection() {
140        // This test checks the detection logic without requiring actual files
141        let temp_dir = std::env::temp_dir();
142        assert!(!is_helm_chart(&temp_dir)); // temp dir is not a Helm chart
143    }
144
145    #[test]
146    fn test_helm_availability() {
147        // Just verify the function runs without panicking
148        let _available = is_helm_available();
149    }
150}