syncable_cli/handlers/
tools.rs

1use crate::{
2    analyzer::{tool_installer::ToolInstaller, dependency_parser::Language},
3    cli::{ToolsCommand, OutputFormat},
4};
5use std::collections::HashMap;
6use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
7
8pub async fn handle_tools(command: ToolsCommand) -> crate::Result<()> {
9    match command {
10        ToolsCommand::Status { format, languages } => handle_tools_status(format, languages),
11        ToolsCommand::Install { languages, include_owasp, dry_run, yes } => {
12            handle_tools_install(languages, include_owasp, dry_run, yes)
13        }
14        ToolsCommand::Verify { languages, verbose } => handle_tools_verify(languages, verbose),
15        ToolsCommand::Guide { languages, platform } => handle_tools_guide(languages, platform),
16    }
17}
18
19fn handle_tools_status(format: OutputFormat, languages: Option<Vec<String>>) -> crate::Result<()> {
20    let installer = ToolInstaller::new();
21    
22    // Determine which languages to check
23    let langs_to_check = get_languages_to_check(languages);
24    
25    println!("šŸ”§ Checking vulnerability scanning tools status...\n");
26    
27    match format {
28        OutputFormat::Table => display_status_table(&installer, &langs_to_check)?,
29        OutputFormat::Json => display_status_json(&installer, &langs_to_check),
30    }
31    
32    Ok(())
33}
34
35fn handle_tools_install(
36    languages: Option<Vec<String>>,
37    include_owasp: bool,
38    dry_run: bool,
39    yes: bool,
40) -> crate::Result<()> {
41    let mut installer = ToolInstaller::new();
42    
43    // Determine which languages to install tools for
44    let langs_to_install = get_languages_to_install(languages);
45    
46    if dry_run {
47        return handle_dry_run(&installer, &langs_to_install, include_owasp);
48    }
49    
50    if !yes && !confirm_installation()? {
51        println!("Installation cancelled.");
52        return Ok(());
53    }
54    
55    println!("šŸ› ļø  Installing vulnerability scanning tools...");
56    
57    match installer.ensure_tools_for_languages(&langs_to_install) {
58        Ok(()) => {
59            println!("āœ… Tool installation completed!");
60            installer.print_tool_status(&langs_to_install);
61            print_setup_instructions();
62        }
63        Err(e) => {
64            eprintln!("āŒ Tool installation failed: {}", e);
65            eprintln!("\nšŸ”§ Manual installation may be required for some tools.");
66            eprintln!("   Run 'sync-ctl tools guide' for manual installation instructions.");
67            return Err(e);
68        }
69    }
70    
71    Ok(())
72}
73
74fn handle_tools_verify(languages: Option<Vec<String>>, verbose: bool) -> crate::Result<()> {
75    let installer = ToolInstaller::new();
76    
77    // Determine which languages to verify
78    let langs_to_verify = get_languages_to_verify(languages);
79    
80    println!("šŸ” Verifying vulnerability scanning tools...\n");
81    
82    let mut all_working = true;
83    
84    for language in &langs_to_verify {
85        let (tool_name, is_working) = get_tool_for_language(&installer, language);
86        
87        print!("  {} {:?}: {}", 
88               if is_working { "āœ…" } else { "āŒ" }, 
89               language,
90               tool_name);
91        
92        if is_working {
93            println!(" - working correctly");
94            
95            if verbose {
96                print_version_info(tool_name);
97            }
98        } else {
99            println!(" - not working or missing");
100            all_working = false;
101        }
102    }
103    
104    if all_working {
105        println!("\nāœ… All tools are working correctly!");
106    } else {
107        println!("\nāŒ Some tools are missing or not working.");
108        println!("   Run 'sync-ctl tools install' to install missing tools.");
109    }
110    
111    Ok(())
112}
113
114fn handle_tools_guide(languages: Option<Vec<String>>, platform: Option<String>) -> crate::Result<()> {
115    let target_platform = platform.unwrap_or_else(|| {
116        match std::env::consts::OS {
117            "macos" => "macOS".to_string(),
118            "linux" => "Linux".to_string(),
119            "windows" => "Windows".to_string(),
120            other => other.to_string(),
121        }
122    });
123    
124    println!("šŸ“š Vulnerability Scanning Tools Installation Guide");
125    println!("Platform: {}", target_platform);
126    println!("{}", "=".repeat(60));
127    
128    let langs_to_show = get_languages_to_show(languages);
129    
130    for language in &langs_to_show {
131        print_language_guide(language, &target_platform);
132    }
133    
134    print_universal_scanners_info();
135    print_general_tips();
136    
137    Ok(())
138}
139
140// Helper functions
141
142fn get_languages_to_check(languages: Option<Vec<String>>) -> Vec<Language> {
143    if let Some(lang_names) = languages {
144        lang_names.iter()
145            .filter_map(|name| Language::from_string(name))
146            .collect()
147    } else {
148        vec![
149            Language::Rust,
150            Language::JavaScript,
151            Language::TypeScript,
152            Language::Python,
153            Language::Go,
154            Language::Java,
155            Language::Kotlin,
156        ]
157    }
158}
159
160fn get_languages_to_install(languages: Option<Vec<String>>) -> Vec<Language> {
161    if let Some(lang_names) = languages {
162        lang_names.iter()
163            .filter_map(|name| Language::from_string(name))
164            .collect()
165    } else {
166        vec![
167            Language::Rust,
168            Language::JavaScript,
169            Language::TypeScript,
170            Language::Python,
171            Language::Go,
172            Language::Java,
173        ]
174    }
175}
176
177fn get_languages_to_verify(languages: Option<Vec<String>>) -> Vec<Language> {
178    if let Some(lang_names) = languages {
179        lang_names.iter()
180            .filter_map(|name| Language::from_string(name))
181            .collect()
182    } else {
183        vec![
184            Language::Rust,
185            Language::JavaScript,
186            Language::TypeScript,
187            Language::Python,
188            Language::Go,
189            Language::Java,
190        ]
191    }
192}
193
194fn get_languages_to_show(languages: Option<Vec<String>>) -> Vec<Language> {
195    if let Some(lang_names) = languages {
196        lang_names.iter()
197            .filter_map(|name| Language::from_string(name))
198            .collect()
199    } else {
200        vec![
201            Language::Rust,
202            Language::JavaScript,
203            Language::TypeScript,
204            Language::Python,
205            Language::Go,
206            Language::Java,
207        ]
208    }
209}
210
211fn get_tool_for_language<'a>(installer: &ToolInstaller, language: &Language) -> (&'a str, bool) {
212    match language {
213        Language::Rust => ("cargo-audit", installer.test_tool_availability("cargo-audit")),
214        Language::JavaScript | Language::TypeScript => ("npm", installer.test_tool_availability("npm")),
215        Language::Python => ("pip-audit", installer.test_tool_availability("pip-audit")),
216        Language::Go => ("govulncheck", installer.test_tool_availability("govulncheck")),
217        Language::Java | Language::Kotlin => ("grype", installer.test_tool_availability("grype")),
218        _ => ("unknown", false),
219    }
220}
221
222fn display_status_table(installer: &ToolInstaller, langs_to_check: &[Language]) -> crate::Result<()> {
223    let mut stdout = StandardStream::stdout(ColorChoice::Always);
224    
225    println!("šŸ“‹ Vulnerability Scanning Tools Status");
226    println!("{}", "=".repeat(50));
227    
228    for language in langs_to_check {
229        let (tool_name, is_available) = get_tool_for_language(installer, language);
230        
231        if tool_name == "unknown" {
232            continue;
233        }
234        
235        print!("  {} {:?}: ", 
236               if is_available { "āœ…" } else { "āŒ" }, 
237               language);
238        
239        if is_available {
240            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
241            print!("{} installed", tool_name);
242        } else {
243            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
244            print!("{} missing", tool_name);
245        }
246        
247        stdout.reset()?;
248        println!();
249    }
250    
251    // Check universal tools
252    println!("\nšŸ” Universal Scanners:");
253    let grype_available = installer.test_tool_availability("grype");
254    print!("  {} Grype: ", if grype_available { "āœ…" } else { "āŒ" });
255    if grype_available {
256        stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
257        println!("installed");
258    } else {
259        stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
260        println!("missing");
261    }
262    stdout.reset()?;
263    
264    Ok(())
265}
266
267fn display_status_json(installer: &ToolInstaller, langs_to_check: &[Language]) {
268    let mut status = HashMap::new();
269    
270    for language in langs_to_check {
271        let (tool_name, is_available) = get_tool_for_language(installer, language);
272        
273        if tool_name == "unknown" {
274            continue;
275        }
276        
277        status.insert(format!("{:?}", language), serde_json::json!({
278            "tool": tool_name,
279            "available": is_available
280        }));
281    }
282    
283    println!("{}", serde_json::to_string_pretty(&status).unwrap());
284}
285
286fn handle_dry_run(installer: &ToolInstaller, langs_to_install: &[Language], include_owasp: bool) -> crate::Result<()> {
287    println!("šŸ” Dry run: Tools that would be installed:");
288    println!("{}", "=".repeat(50));
289    
290    for language in langs_to_install {
291        let (tool_name, is_available) = get_tool_for_language(installer, language);
292        
293        if tool_name == "unknown" {
294            continue;
295        }
296        
297        if !is_available {
298            println!("  šŸ“¦ Would install {} for {:?}", tool_name, language);
299        } else {
300            println!("  āœ… {} already installed for {:?}", tool_name, language);
301        }
302    }
303    
304    if include_owasp && !installer.test_tool_availability("dependency-check") {
305        println!("  šŸ“¦ Would install OWASP Dependency Check (large download)");
306    }
307    
308    Ok(())
309}
310
311fn confirm_installation() -> crate::Result<bool> {
312    use std::io::{self, Write};
313    print!("šŸ”§ Install missing vulnerability scanning tools? [y/N]: ");
314    io::stdout().flush()?;
315    
316    let mut input = String::new();
317    io::stdin().read_line(&mut input)?;
318    
319    Ok(input.trim().to_lowercase().starts_with('y'))
320}
321
322fn print_setup_instructions() {
323    println!("\nšŸ’” Setup Instructions:");
324    println!("  • Add ~/.local/bin to your PATH for manually installed tools");
325    println!("  • Add ~/go/bin to your PATH for Go tools");
326    println!("  • Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):");
327    println!("    export PATH=\"$HOME/.local/bin:$HOME/go/bin:$PATH\"");
328}
329
330fn print_version_info(tool_name: &str) {
331    use std::process::Command;
332    let version_result = match tool_name {
333        "cargo-audit" => Command::new("cargo").args(&["audit", "--version"]).output(),
334        "npm" => Command::new("npm").arg("--version").output(),
335        "pip-audit" => Command::new("pip-audit").arg("--version").output(),
336        "govulncheck" => Command::new("govulncheck").arg("-version").output(),
337        "grype" => Command::new("grype").arg("version").output(),
338        _ => return,
339    };
340    
341    if let Ok(output) = version_result {
342        if output.status.success() {
343            let version = String::from_utf8_lossy(&output.stdout);
344            println!("    Version: {}", version.trim());
345        }
346    }
347}
348
349fn print_language_guide(language: &Language, target_platform: &str) {
350    match language {
351        Language::Rust => {
352            println!("\nšŸ¦€ Rust - cargo-audit");
353            println!("  Install: cargo install cargo-audit");
354            println!("  Usage: cargo audit");
355        }
356        Language::JavaScript | Language::TypeScript => {
357            println!("\n🌐 JavaScript/TypeScript - npm audit");
358            println!("  Install: Download Node.js from https://nodejs.org/");
359            match target_platform {
360                "macOS" => println!("  Package manager: brew install node"),
361                "Linux" => println!("  Package manager: sudo apt install nodejs npm (Ubuntu/Debian)"),
362                _ => {}
363            }
364            println!("  Usage: npm audit");
365        }
366        Language::Python => {
367            println!("\nšŸ Python - pip-audit");
368            println!("  Install: pipx install pip-audit (recommended)");
369            println!("  Alternative: pip3 install --user pip-audit");
370            println!("  Also available: safety (pip install safety)");
371            println!("  Usage: pip-audit");
372        }
373        Language::Go => {
374            println!("\n🐹 Go - govulncheck");
375            println!("  Install: go install golang.org/x/vuln/cmd/govulncheck@latest");
376            println!("  Note: Make sure ~/go/bin is in your PATH");
377            println!("  Usage: govulncheck ./...");
378        }
379        Language::Java => {
380            println!("\nā˜• Java - Multiple options");
381            println!("  Grype (recommended):");
382            match target_platform {
383                "macOS" => println!("    Install: brew install anchore/grype/grype"),
384                "Linux" => println!("    Install: Download from https://github.com/anchore/grype/releases"),
385                _ => println!("    Install: Download from https://github.com/anchore/grype/releases"),
386            }
387            println!("    Usage: grype .");
388            println!("  OWASP Dependency Check:");
389            match target_platform {
390                "macOS" => println!("    Install: brew install dependency-check"),
391                _ => println!("    Install: Download from https://github.com/jeremylong/DependencyCheck/releases"),
392            }
393            println!("    Usage: dependency-check --project myproject --scan .");
394        }
395        _ => {}
396    }
397}
398
399fn print_universal_scanners_info() {
400    println!("\nšŸ” Universal Scanners:");
401    println!("  Grype: Works with multiple ecosystems");
402    println!("  Trivy: Container and filesystem scanning");
403    println!("  Snyk: Commercial solution with free tier");
404}
405
406fn print_general_tips() {
407    println!("\nšŸ’” Tips:");
408    println!("  • Run 'sync-ctl tools status' to check current installation");
409    println!("  • Run 'sync-ctl tools install' for automatic installation");
410    println!("  • Add tool directories to your PATH for easier access");
411}