fundaia 0.52.1

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
//! `exec` and `pull`: one command in a service's running container, and a newer image behind its tag.

use anyhow::Result;
use owo_colors::OwoColorize;

use crate::api::client::ApiClient;
use crate::output::Format;
use crate::render::sanitise;
use crate::render::theme;

/// Prints stdout to stdout and stderr to stderr, so a pipe gets only what the command printed, and
/// says how it ended on stderr. The exit is this command's own class, never the remote one's: the
/// numbers never move. `--json` carries `exitCode`.
pub async fn exec(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    command: &[String],
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client
        .run_in_service(&service.id, &command.join(" "))
        .await?;

    if format.answered_in_json(&answer.raw) {
        return Ok(());
    }

    let run = &answer.body.run;
    if !run.stdout.is_empty() {
        print!("{}", sanitise::with_colour(&run.stdout));
        if !run.stdout.ends_with('\n') {
            println!();
        }
    }
    if !run.stderr.is_empty() {
        eprint!("{}", sanitise::with_colour(&run.stderr));
        if !run.stderr.ends_with('\n') {
            eprintln!();
        }
    }

    let ended = if run.timed_out {
        "tiempo agotado (60 s)".to_owned()
    } else {
        format!("exit {}", run.exit_code)
    };
    let seconds = run.duration_millis as f64 / 1000.0;
    if run.exit_code == 0 && !run.timed_out {
        eprintln!(
            "  {}",
            format!("{ended} · {seconds:.1} s").style(theme::muted())
        );
    } else {
        eprintln!(
            "  {} {}",
            theme::CROSS.style(theme::danger()),
            format!("{ended} · {seconds:.1} s").style(theme::danger())
        );
    }
    if run.truncated {
        eprintln!(
            "  {}",
            "Salida recortada a 64 KB por flujo.".style(theme::warning())
        );
    }
    Ok(())
}

/// `pull`: the registry asked now; a newer image behind the tag is deployed like any other.
pub async fn pull(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client.pull_image(&service.id).await?;

    if format.answered_in_json(&answer.raw) {
        return Ok(());
    }

    let check = &answer.body.check;
    let image = sanitise::plain(&check.image);
    match check.outcome.as_str() {
        "updating" => println!(
            "  {} Hay una imagen nueva de {}: desplegándola. `fundaia watch` la sigue.",
            theme::CHECK.style(theme::success()),
            image.style(theme::strong())
        ),
        "deploying" => println!(
            "  {} Ya hay un despliegue en marcha; baja {} por su cuenta.",
            theme::PENDING.style(theme::muted()),
            image.style(theme::strong())
        ),
        _ => println!(
            "  {} {} ya es la última imagen de su etiqueta.",
            theme::CHECK.style(theme::success()),
            image.style(theme::strong())
        ),
    }
    Ok(())
}