mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
#![allow(dead_code)]

//! Docker container operations

use crate::paths;
use crate::services::docker::types::ContainerInfo;
use anyhow::{Context, Result};
use std::path::Path;
use std::process::Command;
use std::time::Duration;

/// Check if a container is running
///
/// # Arguments
///
/// * `container_name` - Name or ID of the container
pub async fn is_container_running(container_name: &str) -> Result<bool> {
    let output = Command::new("docker")
        .args([
            "ps",
            "--filter",
            &format!("name={}", container_name),
            "--format",
            "{{.Names}}",
        ])
        .output()
        .context("Failed to check container status")?;

    if !output.status.success() {
        return Ok(false);
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    Ok(stdout.contains(container_name))
}

/// List running containers
pub async fn list_containers() -> Result<Vec<ContainerInfo>> {
    let output = Command::new("docker")
        .args(["ps", "--format", "{{.Names}}\t{{.Status}}\t{{.Ports}}"])
        .output()
        .context("Failed to list containers")?;

    if !output.status.success() {
        return Err(anyhow::anyhow!("Failed to list containers"));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let mut containers = Vec::new();

    for line in stdout.lines() {
        let parts: Vec<&str> = line.split('\t').collect();
        if parts.len() >= 2 {
            containers.push(ContainerInfo {
                name: parts[0].to_string(),
                status: parts[1].to_string(),
                ports: parts.get(2).map(|s| s.to_string()),
            });
        }
    }

    Ok(containers)
}

/// Check if docker-compose.yml exists
///
/// # Arguments
///
/// * `compose_file` - Optional custom compose file path
/// * `path` - Path to check (defaults to current directory)
pub fn compose_file_exists(compose_file: Option<&str>, path: Option<&Path>) -> bool {
    let compose_path = if let Some(p) = path {
        p.join(paths::docker::COMPOSE_FILE)
    } else if let Some(file) = compose_file {
        std::path::PathBuf::from(file)
    } else {
        std::path::PathBuf::from(paths::docker::COMPOSE_FILE)
    };

    compose_path.exists()
}

/// Wait for a service to be healthy
///
/// # Arguments
///
/// * `container_name` - Name of the container
/// * `timeout` - Maximum time to wait
pub async fn wait_for_healthy(container_name: &str, timeout: Duration) -> Result<()> {
    let start = std::time::Instant::now();

    while start.elapsed() < timeout {
        if is_container_running(container_name).await? {
            return Ok(());
        }

        tokio::time::sleep(Duration::from_millis(500)).await;
    }

    Err(anyhow::anyhow!(
        "Timeout waiting for container {} to be healthy",
        container_name
    ))
}