use anyhow::Result;
use clap::Parser;
use tracing::{error, info, warn};
use torrust_tracker_deployer_lib::bootstrap::logging::{LogFormat, LogOutput, LoggingBuilder};
use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::run_preflight_cleanup;
#[derive(Parser)]
#[command(name = "lxd-cleanup")]
#[command(about = "Emergency cleanup tool for LXD deployment environments")]
#[command(
long_about = "⚠️ EMERGENCY TOOL - Use 'destroy' + 'purge' commands for normal operations.\n\n\
This tool is ONLY for emergency situations when environment data is missing or corrupted.\n\n\
Removes all infrastructure and artifacts for specified LXD environments.\n\
This includes build/templates/data directories, OpenTofu state, and LXD resources."
)]
struct Cli {
#[arg(
required = true,
help = "One or more LXD environment names to clean up (e.g., cleanup-test, manual-test-mysql)"
)]
environments: Vec<String>,
#[arg(
long,
default_value = "pretty",
help = "Logging format: pretty, json, or compact"
)]
log_format: LogFormat,
#[arg(
long,
help = "Dry run mode - show what would be cleaned without making changes"
)]
dry_run: bool,
}
#[allow(clippy::too_many_lines)]
fn main() -> Result<()> {
let cli = Cli::parse();
LoggingBuilder::new(std::path::Path::new("./data/logs/manual-cleanup"))
.with_format(cli.log_format.clone())
.with_output(LogOutput::FileAndStderr)
.init();
info!(
application = "torrust_tracker_deployer",
tool = "lxd_cleanup",
log_format = ?cli.log_format,
dry_run = cli.dry_run,
environment_count = cli.environments.len(),
"Starting LXD emergency cleanup"
);
if cli.dry_run {
warn!("🔍 DRY RUN MODE - No changes will be made");
warn!("Remove --dry-run flag to perform actual cleanup");
}
let total_environments = cli.environments.len();
let mut successful_cleanups = 0;
let mut failed_cleanups = Vec::new();
print_header(total_environments, cli.dry_run);
for (index, environment_name) in cli.environments.iter().enumerate() {
let env_num = index + 1;
println!("[{env_num}/{total_environments}] Cleaning environment: {environment_name}");
info!(
operation = "environment_cleanup",
environment = environment_name,
progress = format!("{}/{}", env_num, total_environments),
"Starting cleanup for environment"
);
if cli.dry_run {
print_dry_run_info(environment_name);
successful_cleanups += 1;
continue;
}
match run_preflight_cleanup(environment_name) {
Ok(()) => {
println!(" ✅ Successfully cleaned: {environment_name}");
info!(
operation = "environment_cleanup",
environment = environment_name,
status = "success",
"Environment cleanup completed successfully"
);
successful_cleanups += 1;
}
Err(e) => {
println!(" ❌ Failed to clean: {environment_name}");
println!(" Error: {e}");
error!(
operation = "environment_cleanup",
environment = environment_name,
status = "failed",
error = %e,
"Environment cleanup failed"
);
failed_cleanups.push((environment_name.clone(), e));
}
}
println!();
}
print_summary(total_environments, successful_cleanups, &failed_cleanups);
if !failed_cleanups.is_empty() {
error!(
operation = "lxd_cleanup",
status = "partial_failure",
successful = successful_cleanups,
failed = failed_cleanups.len(),
"LXD cleanup completed with failures"
);
return Err(anyhow::anyhow!(
"Cleanup failed for {} environment(s): {}",
failed_cleanups.len(),
failed_cleanups
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>()
.join(", ")
));
}
info!(
operation = "lxd_cleanup",
status = "success",
cleaned_environments = successful_cleanups,
"LXD cleanup completed successfully"
);
print_completion_message(cli.dry_run);
Ok(())
}
fn print_header(total_environments: usize, dry_run: bool) {
println!("\n========================================");
println!("🧹 LXD Emergency Cleanup Tool");
println!("========================================");
println!("⚠️ For normal operations, use: 'destroy' + 'purge'");
println!("LXD environments to clean: {total_environments}");
if dry_run {
println!("Mode: DRY RUN (no changes will be made)");
}
println!();
}
fn print_dry_run_info(environment_name: &str) {
println!(" ℹ️ Would clean:");
println!(" - Build directory: ./build/{environment_name}");
println!(" - Templates directory: ./templates/{environment_name}");
println!(" - Data directory: ./data/{environment_name}");
println!(" - OpenTofu state (if exists)");
println!(" - LXD instance: torrust-tracker-vm-{environment_name}");
println!(" - LXD profile: torrust-profile-{environment_name}");
println!(" ✅ Dry run completed for: {environment_name}");
}
fn print_summary(
total_environments: usize,
successful_cleanups: usize,
failed_cleanups: &[(String, anyhow::Error)],
) {
println!("========================================");
println!("📊 Cleanup Summary");
println!("========================================");
println!("Total environments: {total_environments}");
println!("✅ Successful: {successful_cleanups}");
println!("❌ Failed: {}", failed_cleanups.len());
println!();
if !failed_cleanups.is_empty() {
println!("Failed environments:");
for (env_name, error) in failed_cleanups {
println!(" - {env_name}: {error}");
}
println!();
}
}
fn print_completion_message(dry_run: bool) {
if dry_run {
println!("✅ Dry run completed successfully");
println!(" Run without --dry-run to perform actual cleanup");
} else {
println!("✅ All environments cleaned successfully");
}
println!();
}