fundaia 0.4.0

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
use anyhow::{bail, Result};
use owo_colors::OwoColorize;

use crate::api::client::ApiClient;
use crate::api::models::SettingsPatch;
use crate::render::theme;

/// How much of the machine is left.
///
/// The question "how many services can I have" has an answer that surprises
/// people: nothing is created per service in Cloudflare, so the ceiling is this
/// machine's memory and it is much closer than anybody expects.
pub async fn capacity(client: &ApiClient) -> Result<()> {
    let capacity = client.capacity().await?;

    println!();
    println!(
        "  {} de {} MB libres  {}",
        capacity.available_memory_mb.style(theme::strong()),
        capacity.total_memory_mb,
        format!(
            "({} para el sistema, {} comprometidos)",
            capacity.reserved_memory_mb, capacity.committed_memory_mb
        )
        .style(theme::muted()),
    );
    println!(
        "  caben {} servicios más a 512 MB",
        capacity.remaining_at_default_size.style(theme::strong()),
    );
    println!(
        "  {}",
        format!(
            "puertos {} de {}",
            capacity.ports_in_use, capacity.ports_total
        )
        .style(theme::muted()),
    );

    if capacity.running_low {
        println!();
        println!(
            "  {} Queda poco sitio en esta máquina.",
            theme::PENDING.style(theme::warning()),
        );
    }
    println!();

    Ok(())
}

/// Changes one or more settings, leaving the rest alone.
///
/// Every field is optional and nothing is sent that was not asked for, so this
/// can never overwrite a setting somebody changed in the browser a moment ago
/// with whatever the terminal happened to be holding.
#[allow(clippy::too_many_arguments)]
pub async fn set_settings(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    patch: SettingsPatch,
) -> Result<()> {
    let project = client.find_project(project_reference).await?;
    let service = client.find_service(&project.id, service_reference).await?;

    client.update_settings(&service.id, &patch).await?;

    println!(
        "  {} Ajustes de {} guardados",
        theme::CHECK.style(theme::success()),
        service.name.style(theme::strong()),
    );
    println!(
        "  {}",
        "El próximo despliegue los recoge; el contenedor actual no.".style(theme::muted()),
    );

    Ok(())
}

/// Turns encryption at rest on or off for a storage service's buckets.
pub async fn encryption(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    enabled: bool,
) -> Result<()> {
    let project = client.find_project(project_reference).await?;
    let service = client.find_service(&project.id, service_reference).await?;

    println!(
        "  {}",
        if enabled {
            "Cifrando… se reescribe lo que ya está guardado.".style(theme::muted())
        } else {
            "Quitando el cifrado por defecto…".style(theme::muted())
        }
    );

    let outcome = client.set_encryption(&service.id, enabled).await?;

    if !outcome.enabled {
        println!(
            "  {} Cifrado desactivado para lo nuevo en {} bucket(s)",
            theme::CHECK.style(theme::success()),
            outcome.buckets.len(),
        );
        return Ok(());
    }

    println!(
        "  {} Cifrado activado en {} bucket(s), {} objeto(s) reescrito(s)",
        theme::CHECK.style(theme::success()),
        outcome.buckets.len(),
        outcome.re_encrypted,
    );
    println!(
        "  {}",
        "La clave vive en el llavero de la plataforma. Si se pierde, los objetos no se recuperan."
            .style(theme::muted()),
    );

    Ok(())
}

/// Publishes a service on the internet, or takes it off.
///
/// The project network is never in question: every service is always reachable
/// there by name, which is what makes a private database useful rather than
/// unreachable. What this turns off is the hostname on top of that.
pub async fn expose(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    public: bool,
) -> Result<()> {
    let project = client.find_project(project_reference).await?;
    let service = client.find_service(&project.id, service_reference).await?;

    let outcome = client.set_public_access(&service.id, public).await?;

    if !public {
        println!(
            "  {} {} sale de internet",
            theme::CHECK.style(theme::success()),
            service.name.style(theme::strong()),
        );
        println!(
            "  {}",
            "Sus dominios se han eliminado. Los demás servicios del proyecto lo siguen alcanzando \
             por su nombre."
                .style(theme::muted()),
        );
        return Ok(());
    }

    println!(
        "  {} {} vuelve a ser público",
        theme::CHECK.style(theme::success()),
        service.name.style(theme::strong()),
    );

    if outcome.domains.is_empty() {
        println!(
            "  {}",
            "Tendrá un dominio en el próximo despliegue, o añádele uno con `domains add`."
                .style(theme::muted()),
        );
    } else {
        for domain in &outcome.domains {
            println!("    https://{}", domain.host);
        }
    }

    Ok(())
}

/// Removes a service and everything under it.
///
/// The name has to be typed back. A destructive command whose only guard is a
/// flag is one that gets run with the flag out of habit.
pub async fn delete(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    confirmation: Option<&str>,
) -> Result<()> {
    let project = client.find_project(project_reference).await?;
    let service = client.find_service(&project.id, service_reference).await?;

    if confirmation != Some(service.name.as_str()) {
        bail!(
            "escribe `--confirm {}` para eliminarlo: se van sus despliegues, dominios y variables",
            service.name
        );
    }

    client.delete_service(&service.service_id).await?;

    println!(
        "  {} {} eliminado",
        theme::CHECK.style(theme::success()),
        service.name.style(theme::strong()),
    );

    Ok(())
}