syncable_cli/analyzer/tool_management/
installer.rs

1use crate::analyzer::dependency_parser::Language;
2use crate::analyzer::tool_management::{ToolDetector, InstallationSource};
3use crate::error::{AnalysisError, IaCGeneratorError, Result};
4use log::{info, warn, debug};
5use std::collections::HashMap;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum ToolInstallationError {
10    #[error("Installation failed: {0}")]
11    InstallationFailed(String),
12    
13    #[error("Tool not supported on this platform: {0}")]
14    UnsupportedPlatform(String),
15    
16    #[error("Command execution failed: {0}")]
17    CommandFailed(String),
18    
19    #[error("IO error: {0}")]
20    Io(#[from] std::io::Error),
21}
22
23/// Tool installer for vulnerability scanning dependencies
24pub struct ToolInstaller {
25    installed_tools: HashMap<String, bool>,
26    tool_detector: ToolDetector,
27}
28
29impl ToolInstaller {
30    pub fn new() -> Self {
31        Self {
32            installed_tools: HashMap::new(),
33            tool_detector: ToolDetector::new(),
34        }
35    }
36    
37    /// Ensure all required tools for vulnerability scanning are available
38    pub fn ensure_tools_for_languages(&mut self, languages: &[Language]) -> Result<()> {
39        for language in languages {
40            match language {
41                Language::Rust => self.ensure_cargo_audit()?,
42                Language::JavaScript | Language::TypeScript => {
43                    if self.ensure_bun().is_err() {
44                        self.ensure_npm()?;
45                    }
46                },
47                Language::Python => self.ensure_pip_audit()?,
48                Language::Go => self.ensure_govulncheck()?,
49                Language::Java | Language::Kotlin => self.ensure_grype()?,
50                _ => {}
51            }
52        }
53        Ok(())
54    }
55    
56    /// Check if a tool is installed and available
57    fn is_tool_installed(&mut self, tool: &str) -> bool {
58        let status = self.tool_detector.detect_tool(tool);
59        self.installed_tools.insert(tool.to_string(), status.available);
60        status.available
61    }
62    
63    /// Test if a tool is available by running version command
64    pub fn test_tool_availability(&mut self, tool: &str) -> bool {
65        self.is_tool_installed(tool)
66    }
67    
68    /// Get installation status summary
69    pub fn get_tool_status(&self) -> HashMap<String, bool> {
70        self.installed_tools.clone()
71    }
72    
73    /// Print tool installation status with detailed information
74    pub fn print_tool_status(&mut self, languages: &[Language]) {
75        println!("\nšŸ”§ Vulnerability Scanning Tools Status:");
76        println!("{}", "=".repeat(50));
77        
78        let tool_statuses = self.tool_detector.detect_all_vulnerability_tools(languages);
79        
80        for language in languages {
81            match language {
82                Language::Rust => {
83                    self.print_single_tool_status("cargo-audit", &tool_statuses, language);
84                }
85                Language::JavaScript | Language::TypeScript => {
86                    let js_tools = ["bun", "npm", "yarn", "pnpm"];
87                    for tool in &js_tools {
88                        if let Some(status) = tool_statuses.get(*tool) {
89                            self.print_js_tool_status(tool, status, language);
90                        }
91                    }
92                }
93                Language::Python => {
94                    self.print_single_tool_status("pip-audit", &tool_statuses, language);
95                }
96                Language::Go => {
97                    self.print_single_tool_status("govulncheck", &tool_statuses, language);
98                }
99                Language::Java | Language::Kotlin => {
100                    self.print_single_tool_status("grype", &tool_statuses, language);
101                }
102                _ => continue,
103            }
104        }
105        println!();
106    }
107    
108    fn print_single_tool_status(
109        &self,
110        tool_name: &str,
111        tool_statuses: &HashMap<String, crate::analyzer::tool_management::ToolStatus>,
112        language: &Language,
113    ) {
114        if let Some(status) = tool_statuses.get(tool_name) {
115            let status_icon = if status.available { "āœ…" } else { "āŒ" };
116            print!("  {} {:?}: {} {}", status_icon, language, tool_name, 
117                   if status.available { "installed" } else { "missing" });
118            
119            if status.available {
120                if let Some(ref version) = status.version {
121                    print!(" (v{})", version);
122                }
123                if let Some(ref path) = status.path {
124                    print!(" at {}", path.display());
125                }
126                match &status.installation_source {
127                    InstallationSource::SystemPath => print!(" [system]"),
128                    InstallationSource::UserLocal => print!(" [user]"),
129                    InstallationSource::CargoHome => print!(" [cargo]"),
130                    InstallationSource::GoHome => print!(" [go]"),
131                    InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
132                    InstallationSource::Manual => print!(" [manual]"),
133                    InstallationSource::NotFound => {},
134                }
135            } else {
136                print!(" - Install with: ");
137                match tool_name {
138                    "cargo-audit" => print!("cargo install cargo-audit"),
139                    "pip-audit" => print!("pip install pip-audit or pipx install pip-audit"),
140                    "govulncheck" => print!("go install golang.org/x/vuln/cmd/govulncheck@latest"),
141                    "grype" => print!("brew install grype or download from GitHub"),
142                    _ => print!("check documentation"),
143                }
144            }
145            println!();
146        }
147    }
148    
149    fn print_js_tool_status(
150        &self,
151        tool_name: &str,
152        status: &crate::analyzer::tool_management::ToolStatus,
153        language: &Language,
154    ) {
155        let status_icon = if status.available { "āœ…" } else { "āŒ" };
156        print!("  {} {:?}: {} {}", status_icon, language, tool_name, 
157               if status.available { "installed" } else { "missing" });
158        
159        if status.available {
160            if let Some(ref version) = status.version {
161                print!(" (v{})", version);
162            }
163            if let Some(ref path) = status.path {
164                print!(" at {}", path.display());
165            }
166            match &status.installation_source {
167                InstallationSource::SystemPath => print!(" [system]"),
168                InstallationSource::UserLocal => print!(" [user]"),
169                InstallationSource::CargoHome => print!(" [cargo]"),
170                InstallationSource::GoHome => print!(" [go]"),
171                InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
172                InstallationSource::Manual => print!(" [manual]"),
173                InstallationSource::NotFound => {},
174            }
175        } else {
176            print!(" - Install with: ");
177            match tool_name {
178                "bun" => print!("curl -fsSL https://bun.sh/install | bash"),
179                "npm" => print!("Install Node.js from https://nodejs.org/"),
180                "yarn" => print!("npm install -g yarn"),
181                "pnpm" => print!("npm install -g pnpm"),
182                _ => print!("check documentation"),
183            }
184        }
185        println!();
186    }
187    
188    // Installation methods - these will be moved to installers/ modules
189    fn ensure_cargo_audit(&mut self) -> Result<()> {
190        use crate::analyzer::tool_management::installers::rust::install_cargo_audit;
191        install_cargo_audit(&mut self.tool_detector, &mut self.installed_tools)
192    }
193    
194    fn ensure_npm(&mut self) -> Result<()> {
195        use crate::analyzer::tool_management::installers::javascript::ensure_npm;
196        ensure_npm(&mut self.tool_detector, &mut self.installed_tools)
197    }
198    
199    fn ensure_bun(&mut self) -> Result<()> {
200        use crate::analyzer::tool_management::installers::javascript::install_bun;
201        install_bun(&mut self.tool_detector, &mut self.installed_tools)
202    }
203    
204    fn ensure_pip_audit(&mut self) -> Result<()> {
205        use crate::analyzer::tool_management::installers::python::install_pip_audit;
206        install_pip_audit(&mut self.tool_detector, &mut self.installed_tools)
207    }
208    
209    fn ensure_govulncheck(&mut self) -> Result<()> {
210        use crate::analyzer::tool_management::installers::go::install_govulncheck;
211        install_govulncheck(&mut self.tool_detector, &mut self.installed_tools)
212    }
213    
214    fn ensure_grype(&mut self) -> Result<()> {
215        use crate::analyzer::tool_management::installers::java::install_grype;
216        install_grype(&mut self.tool_detector, &mut self.installed_tools)
217    }
218}