mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Infrastructure command handlers
//!
//! Orchestrates both control plane (monorepo) and project-level Docker infrastructure management.

#![allow(dead_code)]

use super::types::{InfrastructureAction, InfrastructureArgs};
use crate::context::CliContext;
use crate::services::{ConfigService, DockerService, ProjectService};
use anyhow::Result;

/// Handle infrastructure command - Manages Docker infrastructure for control plane or project
pub async fn handle_infrastructure(ctx: &mut CliContext, args: &InfrastructureArgs) -> Result<()> {
    println!();
    let docker = ctx.docker();

    // Check Docker availability
    if docker.check_installation().is_err() || docker.check_daemon().is_err() {
        anyhow::bail!("Docker is not available. Please install Docker and start the daemon.");
    }

    // Detect context: project vs control plane
    let current_dir = std::env::current_dir()?;
    match ProjectService::detect(&current_dir) {
        Ok(project) => {
            println!("🤖 Project Infrastructure - {}", project.name()?);
            println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            println!();
            handle_project_infrastructure(ctx, args, &project).await?;
        }
        Err(_) => {
            println!("🔧 Control Plane Infrastructure");
            println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            println!();
            handle_control_plane_infrastructure(docker, args).await?;
        }
    }
    Ok(())
}

/// Handle project-level infrastructure
async fn handle_project_infrastructure(
    _ctx: &mut CliContext,
    args: &InfrastructureArgs,
    project: &ProjectService,
) -> Result<()> {
    // Load project config to get docker settings
    let config = ConfigService::load_from(&project.config_path()).await?;

    // Setup Docker service with project's compose file
    let compose_path = project.root().join(&config.docker.compose_file);
    if !compose_path.exists() {
        anyhow::bail!("Docker Compose file not found: {}", compose_path.display());
    }
    let docker = DockerService::with_compose_file(compose_path.to_string_lossy().to_string());

    // Get services to manage
    let services_to_manage = if args.services.is_empty() {
        // Default to robot services (minimal set for on-robot execution)
        config.docker.robot.clone()
    } else {
        args.services.clone()
    };

    match args.action {
        InfrastructureAction::Start => handle_start_project(&docker, &services_to_manage).await?,
        InfrastructureAction::Stop => handle_stop(&docker, &services_to_manage).await?,
        InfrastructureAction::Restart => handle_restart(&docker, &services_to_manage).await?,
        InfrastructureAction::Status => handle_status(&docker, &services_to_manage).await?,
        InfrastructureAction::Logs => handle_logs(&docker, &args.services).await?,
    }

    Ok(())
}

/// Handle control plane infrastructure (monorepo)
async fn handle_control_plane_infrastructure(docker: &DockerService, args: &InfrastructureArgs) -> Result<()> {
    match args.action {
        InfrastructureAction::Start => handle_start(docker, &args.services).await?,
        InfrastructureAction::Stop => handle_stop(docker, &args.services).await?,
        InfrastructureAction::Restart => handle_restart(docker, &args.services).await?,
        InfrastructureAction::Status => handle_status(docker, &args.services).await?,
        InfrastructureAction::Logs => handle_logs(docker, &args.services).await?,
    }

    Ok(())
}

/// Handle start action
async fn handle_start(docker: &DockerService, services: &[String]) -> Result<()> {
    println!("Starting infrastructure services...\n");
    if services.is_empty() {
        docker.compose_up(true).await?;
        println!("✅ All services started\n");
    } else {
        for service in services {
            println!("Starting {}...", service);
            docker.compose_up_service(service, true).await?;
            println!("{} started", service);
        }
        println!();
    }
    Ok(())
}

/// Handle start action for project infrastructure
async fn handle_start_project(docker: &DockerService, services: &[String]) -> Result<()> {
    if services.is_empty() {
        println!("No services configured\n");
        return Ok(());
    }
    println!("Starting robot infrastructure...\n");
    for service in services {
        println!("  Starting {}...", service);
        docker.compose_up_service(service, true).await?;
        println!("{} started", service);
    }
    println!("\n✅ All services started successfully\n");
    println!("💡 Tip: Use 'mecha10 infrastructure status' to check service health");
    Ok(())
}

/// Handle stop action
async fn handle_stop(docker: &DockerService, services: &[String]) -> Result<()> {
    println!("Stopping infrastructure services...\n");
    if services.is_empty() {
        docker.compose_down().await?;
        println!("✅ All services stopped\n");
    } else {
        for service in services {
            println!("Stopping {}...", service);
            docker.compose_stop(Some(service)).await?;
            println!("{} stopped", service);
        }
        println!();
    }
    Ok(())
}

/// Handle restart action
async fn handle_restart(docker: &DockerService, services: &[String]) -> Result<()> {
    println!("Restarting infrastructure services...\n");
    if services.is_empty() {
        docker.compose_down().await?;
        docker.compose_up(true).await?;
        println!("✅ All services restarted\n");
    } else {
        for service in services {
            println!("Restarting {}...", service);
            docker.compose_restart(Some(service)).await?;
            println!("{} restarted", service);
        }
        println!();
    }
    Ok(())
}

/// Handle status action
async fn handle_status(docker: &DockerService, services: &[String]) -> Result<()> {
    println!("Infrastructure Status:\n");
    let containers = docker.list_containers().await?;

    if services.is_empty() {
        for container in &containers {
            let running = container.status.to_lowercase().contains("up");
            let status = if running { "✅ Running" } else { "⬜ Stopped" };
            println!("  {} {} ({})", status, container.name, container.status);
        }
    } else {
        for service in services {
            let container = containers.iter().find(|c| c.name.contains(service));
            let running = container
                .map(|c| c.status.to_lowercase().contains("up"))
                .unwrap_or(false);
            let status = if running { "✅ Running" } else { "⬜ Stopped" };
            let detail = container.map(|c| c.status.as_str()).unwrap_or("Not found");
            println!("  {} {} ({})", status, service, detail);
        }
    }
    println!();
    Ok(())
}

/// Handle logs action
async fn handle_logs(docker: &DockerService, services: &[String]) -> Result<()> {
    if services.is_empty() {
        anyhow::bail!("Please specify a service to view logs.\nExample: mecha10 infrastructure logs redis");
    }
    let service = &services[0];
    println!("Viewing logs for: {}\n", service);
    docker.compose_logs(Some(service), false, Some(50))?;
    Ok(())
}