use crate::error::{CliError, Result};
use colored::Colorize;
use lmrc_config_validator::LmrcConfig;
use lmrc_hetzner::{HetznerClient, ServerManager, NetworkManager, LoadBalancerManager, FirewallManager, SshKeyManager};
use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;
pub async fn execute(skip_confirmation: bool, project: Option<String>) -> Result<()> {
println!("{}", "Destroying Infrastructure".red().bold());
println!();
let config_path = if let Some(p) = project {
PathBuf::from(p)
} else {
PathBuf::from("lmrc.toml")
};
if !config_path.exists() {
return Err(CliError::Config(format!(
"Configuration file not found: {}",
config_path.display()
)));
}
let config_str = fs::read_to_string(&config_path).map_err(|e| {
CliError::IoError(format!("Failed to read config file: {}", e))
})?;
let config: LmrcConfig = toml::from_str(&config_str).map_err(|e| {
CliError::Config(format!("Failed to parse config: {}", e))
})?;
let hetzner_token = std::env::var("HETZNER_API_TOKEN").map_err(|_| {
CliError::MissingEnvironmentVariable(
"HETZNER_API_TOKEN is required. Set it with: export HETZNER_API_TOKEN=your_token"
.to_string(),
)
})?;
let client = HetznerClient::builder()
.api_token(hetzner_token)
.build()
.map_err(|e| CliError::Infrastructure(format!("Failed to create Hetzner client: {}", e)))?;
println!("{}", "Discovering infrastructure...".cyan());
println!();
let server_manager = ServerManager::new(client.clone());
let network_manager = NetworkManager::new(client.clone());
let lb_manager = LoadBalancerManager::new(client.clone());
let firewall_manager = FirewallManager::new(client.clone());
let ssh_key_manager = SshKeyManager::new(client.clone());
let project_label = format!("project={}", config.project.name);
let all_servers = server_manager
.list_servers()
.await
.map_err(|e| CliError::Infrastructure(format!("Failed to list servers: {}", e)))?;
let servers: Vec<_> = all_servers
.into_iter()
.filter(|s| {
s.labels.get("project").map(|v| v.as_str()) == Some(&config.project.name)
})
.collect();
let all_lbs = lb_manager
.list_load_balancers()
.await
.map_err(|e| CliError::Infrastructure(format!("Failed to list load balancers: {}", e)))?;
let load_balancers: Vec<_> = all_lbs
.into_iter()
.filter(|lb| {
lb.labels.get("project").map(|v| v.as_str()) == Some(&config.project.name)
})
.collect();
let all_networks = network_manager
.list_networks()
.await
.map_err(|e| CliError::Infrastructure(format!("Failed to list networks: {}", e)))?;
let networks: Vec<_> = all_networks
.into_iter()
.filter(|n| {
n.labels.get("project").map(|v| v.as_str()) == Some(&config.project.name)
})
.collect();
let all_firewalls = firewall_manager
.list_firewalls()
.await
.map_err(|e| CliError::Infrastructure(format!("Failed to list firewalls: {}", e)))?;
let firewalls: Vec<_> = all_firewalls
.into_iter()
.filter(|f| {
f.labels.get("project").map(|v| v.as_str()) == Some(&config.project.name)
})
.collect();
let all_ssh_keys = ssh_key_manager
.list_ssh_keys()
.await
.map_err(|e| CliError::Infrastructure(format!("Failed to list SSH keys: {}", e)))?;
let ssh_keys: Vec<_> = all_ssh_keys
.into_iter()
.filter(|k| {
k.labels.get("project").map(|v| v.as_str()) == Some(&config.project.name)
})
.collect();
if servers.is_empty()
&& load_balancers.is_empty()
&& networks.is_empty()
&& firewalls.is_empty()
&& ssh_keys.is_empty()
{
println!("{}", "No infrastructure found to destroy".yellow());
println!(" Project: {}", config.project.name);
return Ok(());
}
println!(
"{}",
format!("Found infrastructure for project: {}", config.project.name)
.yellow()
.bold()
);
println!();
if !servers.is_empty() {
println!(" {} Servers ({}):", "●".red(), servers.len());
for server in &servers {
println!(
" - {} (ID: {}, Type: {}, Status: {})",
server.name.bright_white(),
server.id,
server.server_type.name,
server.status.cyan()
);
}
}
if !load_balancers.is_empty() {
println!(" {} Load Balancers ({}):", "●".red(), load_balancers.len());
for lb in &load_balancers {
println!(
" - {} (ID: {}, Type: {})",
lb.name.bright_white(),
lb.id,
lb.load_balancer_type.name
);
}
}
if !networks.is_empty() {
println!(" {} Networks ({}):", "●".red(), networks.len());
for network in &networks {
println!(
" - {} (ID: {}, Subnet: {})",
network.name.bright_white(),
network.id,
network.ip_range
);
}
}
if !firewalls.is_empty() {
println!(" {} Firewalls ({}):", "●".red(), firewalls.len());
for firewall in &firewalls {
println!(
" - {} (ID: {}, Rules: {})",
firewall.name.bright_white(),
firewall.id,
firewall.rules.len()
);
}
}
if !ssh_keys.is_empty() {
println!(" {} SSH Keys ({}):", "●".red(), ssh_keys.len());
for key in &ssh_keys {
println!(" - {} (ID: {})", key.name.bright_white(), key.id);
}
}
println!();
if !skip_confirmation {
print!(
"{}",
"⚠️ This will permanently delete all resources listed above. Are you sure? (yes/no): "
.yellow()
.bold()
);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.map_err(|e| CliError::IoError(format!("Failed to read input: {}", e)))?;
let input = input.trim().to_lowercase();
if input != "yes" && input != "y" {
println!("{}", "Destruction cancelled".green());
return Ok(());
}
}
println!();
println!("{}", "Starting destruction process...".red().bold());
println!();
if !servers.is_empty() {
println!("{}", "Deleting servers...".cyan());
for server in servers {
print!(" Deleting {} (ID: {})... ", server.name, server.id);
io::stdout().flush().unwrap();
server_manager
.delete_server(server.id)
.await
.map_err(|e| {
CliError::Infrastructure(format!(
"Failed to delete server {}: {}",
server.name, e
))
})?;
println!("{}", "✓".green());
}
println!();
}
if !load_balancers.is_empty() {
println!("{}", "Deleting load balancers...".cyan());
for lb in load_balancers {
print!(" Deleting {} (ID: {})... ", lb.name, lb.id);
io::stdout().flush().unwrap();
lb_manager
.delete_load_balancer(lb.id)
.await
.map_err(|e| {
CliError::Infrastructure(format!(
"Failed to delete load balancer {}: {}",
lb.name, e
))
})?;
println!("{}", "✓".green());
}
println!();
}
if !firewalls.is_empty() {
println!("{}", "Deleting firewalls...".cyan());
for firewall in firewalls {
print!(" Deleting {} (ID: {})... ", firewall.name, firewall.id);
io::stdout().flush().unwrap();
firewall_manager
.delete_firewall(firewall.id)
.await
.map_err(|e| {
CliError::Infrastructure(format!(
"Failed to delete firewall {}: {}",
firewall.name, e
))
})?;
println!("{}", "✓".green());
}
println!();
}
if !networks.is_empty() {
println!("{}", "Deleting networks...".cyan());
for network in networks {
print!(" Deleting {} (ID: {})... ", network.name, network.id);
io::stdout().flush().unwrap();
network_manager
.delete_network(network.id)
.await
.map_err(|e| {
CliError::Infrastructure(format!(
"Failed to delete network {}: {}",
network.name, e
))
})?;
println!("{}", "✓".green());
}
println!();
}
if !ssh_keys.is_empty() {
println!("{}", "Deleting SSH keys...".cyan());
for key in ssh_keys {
print!(" Deleting {} (ID: {})... ", key.name, key.id);
io::stdout().flush().unwrap();
client
.delete(&format!("/ssh_keys/{}", key.id))
.await
.map_err(|e| {
CliError::Infrastructure(format!("Failed to delete SSH key {}: {}", key.name, e))
})?;
println!("{}", "✓".green());
}
println!();
}
println!();
println!(
"{}",
"✓ All infrastructure successfully destroyed".green().bold()
);
println!();
println!("Project '{}' cleanup complete", config.project.name);
Ok(())
}