syncable_cli/analyzer/tool_management/
installer.rs1use 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
23pub 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 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 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 pub fn test_tool_availability(&mut self, tool: &str) -> bool {
65 self.is_tool_installed(tool)
66 }
67
68 pub fn get_tool_status(&self) -> HashMap<String, bool> {
70 self.installed_tools.clone()
71 }
72
73 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 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}