tatara-cli 0.2.213

CLI binary for tatara — Nix-native workload orchestrator
use anyhow::{bail, Context, Result};

use super::context::{active_endpoint, endpoint_to_server};
use super::output::{build_table, human_duration_since, render_value, status_cell, OutputFormat};

pub async fn list(output: OutputFormat, endpoint: Option<&str>) -> Result<()> {
    let server = endpoint_to_server(&active_endpoint(endpoint));
    let client = reqwest::Client::new();
    let url = format!("http://{}/api/v1/releases", server);

    let resp = client
        .get(&url)
        .send()
        .await
        .context("Failed to connect to tatara server")?;

    if !resp.status().is_success() {
        let body = resp.text().await.unwrap_or_default();
        bail!("Server error: {}", body);
    }

    let releases: Vec<serde_json::Value> = resp.json().await?;

    if releases.is_empty() {
        println!("No releases.");
        return Ok(());
    }

    match output {
        OutputFormat::Json | OutputFormat::Yaml => {
            println!("{}", render_value(&releases, output)?);
        }
        _ => {
            let mut table =
                build_table(&["ID", "NAME", "FLAKE REF", "VERSION", "STATUS", "CREATED"]);
            for rel in &releases {
                let id_str = rel["id"].as_str().unwrap_or("?");
                let short_id = id_str.get(..8).unwrap_or(id_str);
                table.add_row(vec![
                    comfy_table::Cell::new(short_id),
                    comfy_table::Cell::new(rel["name"].as_str().unwrap_or("?")),
                    comfy_table::Cell::new(rel["flake_ref"].as_str().unwrap_or("?")),
                    comfy_table::Cell::new(rel["version"].as_u64().unwrap_or(0).to_string()),
                    status_cell(rel["status"].as_str().unwrap_or("?")),
                    comfy_table::Cell::new(human_duration_since(
                        rel["created_at"].as_str().unwrap_or(""),
                    )),
                ]);
            }
            println!("{table}");
        }
    }
    Ok(())
}

pub async fn get(release_id: &str, output: OutputFormat, endpoint: Option<&str>) -> Result<()> {
    let server = endpoint_to_server(&active_endpoint(endpoint));
    let client = reqwest::Client::new();
    let url = format!("http://{}/api/v1/releases/{}", server, release_id);

    let resp = client
        .get(&url)
        .send()
        .await
        .context("Failed to connect to tatara server")?;

    if resp.status() == 404 {
        bail!("Release not found: {}", release_id);
    }
    if !resp.status().is_success() {
        let body = resp.text().await.unwrap_or_default();
        bail!("Server error: {}", body);
    }

    let release: serde_json::Value = resp.json().await?;

    match output {
        OutputFormat::Json | OutputFormat::Yaml => {
            println!("{}", render_value(&release, output)?);
        }
        _ => {
            println!("Release: {}", release["id"].as_str().unwrap_or("?"));
            println!("  Name:      {}", release["name"].as_str().unwrap_or("?"));
            println!(
                "  Flake Ref: {}",
                release["flake_ref"].as_str().unwrap_or("?")
            );
            if let Some(rev) = release["flake_rev"].as_str() {
                println!("  Flake Rev: {}", rev);
            }
            println!("  Version:   {}", release["version"].as_u64().unwrap_or(0));
            println!("  Status:    {}", release["status"].as_str().unwrap_or("?"));
            println!(
                "  Created:   {}",
                release["created_at"].as_str().unwrap_or("?")
            );
        }
    }
    Ok(())
}

pub async fn promote(release_id: &str, endpoint: Option<&str>, output: OutputFormat) -> Result<()> {
    let server = endpoint_to_server(&active_endpoint(endpoint));
    let client = reqwest::Client::new();
    let url = format!("http://{}/api/v1/releases/{}/promote", server, release_id);

    let resp = client
        .post(&url)
        .send()
        .await
        .context("Failed to connect to tatara server")?;

    if resp.status().is_success() {
        let release: serde_json::Value = resp.json().await?;
        match output {
            OutputFormat::Json | OutputFormat::Yaml => {
                println!("{}", render_value(&release, output)?);
            }
            _ => {
                println!(
                    "Release {} promoted to active",
                    release["id"].as_str().unwrap_or(release_id)
                );
            }
        }
    } else if resp.status() == 404 {
        bail!("Release not found: {}", release_id);
    } else {
        let body = resp.text().await.unwrap_or_default();
        bail!("Server error: {}", body);
    }
    Ok(())
}

pub async fn rollback(
    release_id: &str,
    endpoint: Option<&str>,
    output: OutputFormat,
) -> Result<()> {
    let server = endpoint_to_server(&active_endpoint(endpoint));
    let client = reqwest::Client::new();
    let url = format!("http://{}/api/v1/releases/{}/rollback", server, release_id);

    let resp = client
        .post(&url)
        .send()
        .await
        .context("Failed to connect to tatara server")?;

    if resp.status().is_success() {
        let release: serde_json::Value = resp.json().await?;
        match output {
            OutputFormat::Json | OutputFormat::Yaml => {
                println!("{}", render_value(&release, output)?);
            }
            _ => {
                println!(
                    "Release {} rolled back",
                    release["id"].as_str().unwrap_or(release_id)
                );
            }
        }
    } else if resp.status() == 404 {
        bail!("Release not found: {}", release_id);
    } else {
        let body = resp.text().await.unwrap_or_default();
        bail!("Server error: {}", body);
    }
    Ok(())
}