flyboat 2.0.0

Container environment manager for development
Documentation
use crate::Result;
use crate::cli::CleanArgs;
use crate::config::GlobalConfig;
use crate::config::Paths;
use crate::docker::Engine;
use colored::Colorize;
use std::io::{self, Write};

pub fn execute_clean(args: &CleanArgs, verbose: bool) -> Result<()> {
    let paths = Paths::new()?;
    let global_config = GlobalConfig::load(&paths.global_config())?;
    let engine = global_config.get_container_engine()?;

    if args.danger_purge_all_images {
        return danger_purge_all_images(&paths, &engine);
    }

    remove_images(verbose, &paths, &engine)?;

    remove_dangling_images(&paths, &engine);

    remove_containers(verbose, &paths, &engine);

    Ok(())
}

/// Remove All containers, images and more
fn danger_purge_all_images(paths: &Paths, engine: &Engine) -> Result<()> {
    eprintln!(
        "{}: This will remove:\
  - all stopped containers\
  - all networks not used by at least one container\
  - all images without at least one container associated to them\
  - all build cache",
        "WARNING".red().bold()
    );
    eprint!("Are you sure? Type 'yes' to confirm: ");
    io::stdout().flush().ok();

    let mut input = String::new();
    io::stdin().read_line(&mut input)?;

    if input.trim() != "yes" {
        println!("Aborted.");
        return Ok(());
    }

    println!("{}", "Removing all images...".yellow());

    let status = crate::docker::execute_command_status(
        engine,
        ["image", "prune", "-af"],
        &paths.flyboat_dir(),
    )?;

    if status.success() {
        println!("{} All images removed", "".green());
    }
    Ok(())
}

/// Remove flyboat images
fn remove_images(verbose: bool, paths: &Paths, engine: &Engine) -> Result<()> {
    if verbose {
        eprintln!("Removing flyboat images...");
    }

    let images_output = crate::docker::execute_command_output(
        engine,
        [
            "images",
            "--filter",
            "reference=flyboat-*",
            "--format",
            "{{.ID}}",
        ],
        &paths.flyboat_dir(),
    )?;

    let image_ids = String::from_utf8_lossy(&images_output.stdout);
    let image_ids: Vec<&str> = image_ids.lines().filter(|s| !s.is_empty()).collect();

    if !image_ids.is_empty() {
        println!(
            "{} {} flyboat image(s)...",
            "Removing".blue(),
            image_ids.len()
        );

        for id in &image_ids {
            if verbose {
                eprintln!("  Removing image {}", id);
            }

            crate::docker::execute_command_status(engine, ["rmi", id], &paths.flyboat_dir()).ok();
        }

        println!(
            "{} Removed {} flyboat image(s)",
            "".green(),
            image_ids.len()
        );
    } else {
        println!("No flyboat images to remove");
    }
    Ok(())
}

/// Remove dangling images
fn remove_dangling_images(paths: &Paths, engine: &Engine) {
    println!("Removing dangling images...");
    let status =
        crate::docker::execute_command_status(engine, ["image", "prune"], &paths.flyboat_dir())
            .ok();

    if status.is_some_and(|s| s.success()) {
        println!("{} Dangling images removed", "".green());
    }
}

/// Stop and remove flyboat containers
fn remove_containers(verbose: bool, paths: &Paths, engine: &Engine) {
    if verbose {
        eprintln!("Removing flyboat containers...");
    }

    let containers_output = crate::docker::execute_command_output(
        engine,
        ["ps", "-aq", "--filter", "name=flyboat-"],
        &paths.flyboat_dir(),
    );

    match containers_output {
        Ok(output) => {
            let container_ids = String::from_utf8_lossy(&output.stdout);
            let container_ids: Vec<&str> =
                container_ids.lines().filter(|s| !s.is_empty()).collect();

            if !container_ids.is_empty() {
                println!(
                    "{} {} container(s)...",
                    "Removing".red(),
                    container_ids.len()
                );

                for id in &container_ids {
                    crate::docker::execute_command_status(engine, ["rm", id], &paths.flyboat_dir())
                        .ok();
                }

                println!("Removed {} container(s)", container_ids.len());
            }
        }
        Err(err) => {
            eprintln!("Error: Could not remove containers: {err}");
        }
    }
}