fundaia 0.52.2

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
use std::io::BufRead;

use anyhow::{bail, Context, Result};
use owo_colors::OwoColorize;

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

pub async fn maintenance(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    enabled: bool,
    message: Option<&str>,
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client
        .set_maintenance(&service.id, enabled, message)
        .await?;

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

    if answer.body.maintenance.enabled {
        println!(
            "  {} {} está en mantenimiento",
            theme::CHECK.style(theme::success()),
            sanitise::plain(&service.name).style(theme::strong()),
        );
        println!(
            "  {}",
            "Quien entre verá la página de mantenimiento; el servicio sigue en marcha. \
             `fundaia maintenance … --off` lo vuelve a servir."
                .style(theme::muted()),
        );
    } else {
        println!(
            "  {} {} vuelve a responder",
            theme::CHECK.style(theme::success()),
            sanitise::plain(&service.name).style(theme::strong()),
        );
    }
    Ok(())
}

pub async fn protect(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    username: &str,
    password: Option<&str>,
    format: Format,
) -> Result<()> {
    let password = match password {
        Some(given) => given.to_owned(),
        None => read_password(format)?,
    };
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client
        .protect_service(&service.id, username, &password)
        .await?;

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

    println!(
        "  {} {} pide usuario y contraseña ({})",
        theme::CHECK.style(theme::success()),
        sanitise::plain(&service.name).style(theme::strong()),
        sanitise::plain(
            answer
                .body
                .protection
                .username
                .as_deref()
                .unwrap_or(username)
        )
        .style(theme::muted()),
    );
    Ok(())
}

pub async fn unprotect(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client.unprotect_service(&service.id).await?;

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

    println!(
        "  {} {} ya no pide contraseña",
        theme::CHECK.style(theme::success()),
        sanitise::plain(&service.name).style(theme::strong()),
    );
    Ok(())
}

fn read_password(format: Format) -> Result<String> {
    let interactive = !format.is_json() && std::io::IsTerminal::is_terminal(&std::io::stdin());
    let password = if interactive {
        eprint!("  Contraseña (no se ve al escribir): ");
        rpassword::read_password().context("no se pudo leer la contraseña del teclado")?
    } else {
        let mut line = String::new();
        std::io::stdin()
            .lock()
            .read_line(&mut line)
            .context("no se pudo leer la contraseña de la entrada")?;
        line
    };

    let password = password.trim_end_matches(['\r', '\n']).to_owned();
    if password.is_empty() {
        bail!("sin contraseña no se protege nada: pásala con --password, FUNDAIA_SERVICE_PASSWORD o por la entrada");
    }
    Ok(password)
}