hop-cli 0.2.61

Interact with Hop in your terminal
Documentation
use std::borrow::Borrow;
use std::io::Write;
use std::vec;

use anyhow::{anyhow, Result};
use console::style;
use tabwriter::TabWriter;

use super::types::{
    Container, ContainerState, CreateContainers, Log, LogsResponse, Metrics,
    MultipleContainersResponse, SingleContainer,
};
use crate::commands::ignite::types::Deployment;
use crate::state::http::HttpClient;
use crate::utils::relative_time;
use crate::utils::size::{parse_size, user_friendly_size};

pub async fn create_containers(
    http: &HttpClient,
    deployment_id: &str,
    count: u64,
) -> Result<Vec<Container>> {
    let response = http
        .request::<MultipleContainersResponse>(
            "POST",
            &format!("/ignite/deployments/{deployment_id}/containers"),
            Some((
                serde_json::to_vec(&CreateContainers { count })
                    .unwrap()
                    .into(),
                "application/json",
            )),
        )
        .await?
        .ok_or_else(|| anyhow!("Error while parsing response"))?;

    Ok(response.containers)
}

pub async fn delete_container(
    http: &HttpClient,
    container_id: &str,
    recreate: bool,
) -> Result<Option<Container>> {
    Ok(http
        .request::<SingleContainer>(
            "DELETE",
            &format!("/ignite/containers/{container_id}?recreate={recreate}"),
            None,
        )
        .await?
        .map(|response| response.container))
}

pub async fn get_container(http: &HttpClient, container_id: &str) -> Result<Container> {
    Ok(http
        .request::<SingleContainer>("GET", &format!("/ignite/containers/{container_id}"), None)
        .await?
        .ok_or_else(|| anyhow!("Error while parsing response"))?
        .container)
}

pub async fn get_all_containers(http: &HttpClient, deployment_id: &str) -> Result<Vec<Container>> {
    let response = http
        .request::<MultipleContainersResponse>(
            "GET",
            &format!("/ignite/deployments/{deployment_id}/containers"),
            None,
        )
        .await?
        .ok_or_else(|| anyhow!("Error while parsing response"))?;

    Ok(response.containers)
}

pub async fn get_container_logs(
    http: &HttpClient,
    container_id: &str,
    limit: u64,
    order_by: &str,
) -> Result<Vec<Log>> {
    let response = http
        .request::<LogsResponse>(
            "GET",
            &format!(
                "/ignite/containers/{container_id}/logs?limit={limit}&orderBy={order_by}&offset=0"
            ),
            None,
        )
        .await?
        .ok_or_else(|| anyhow!("Error while parsing response"))?;

    Ok(response.logs)
}

pub const UNAVAILABLE_ELEMENT: &str = "-";

pub fn format_containers(containers: &Vec<Container>, title: bool) -> Vec<String> {
    let mut tw = TabWriter::new(vec![]);

    if title {
        writeln!(tw, "ID\tREGION\tSTATE\tINTERNAL IP\tUPTIME").unwrap();
    }

    for container in containers {
        writeln!(
            tw,
            "{}\t{}\t{}\t{}\t{}",
            container.id,
            container.region,
            container.state,
            container
                .internal_ip
                .as_ref()
                .map(|ip| ip.borrow())
                .unwrap_or_else(|| UNAVAILABLE_ELEMENT),
            if container.state != ContainerState::Running {
                UNAVAILABLE_ELEMENT.to_string()
            } else {
                container
                    .uptime
                    .as_ref()
                    .map(|u| {
                        u.last_start
                            .map(relative_time)
                            .unwrap_or_else(|| UNAVAILABLE_ELEMENT.to_string())
                    })
                    .unwrap_or_else(|| UNAVAILABLE_ELEMENT.to_string())
            },
        )
        .unwrap();
    }

    String::from_utf8(tw.into_inner().unwrap())
        .unwrap()
        .lines()
        .map(std::string::ToString::to_string)
        .collect()
}

pub fn format_logs(log: &[Log], colors: bool, timestamps: bool, details: bool) -> Vec<String> {
    log.iter()
        .map(|log| format_log(log, colors, timestamps, details))
        .collect()
}

fn format_log(log: &Log, colors: bool, timestamps: bool, details: bool) -> String {
    let log_level = if details {
        (if colors {
            match log.level.as_str() {
                "info" | "stdout" => style("INFO").cyan(),
                "error" | "stderr" => style("ERROR").red(),
                // there are only info and error, this is left for future use
                level => style(level).yellow(),
            }
            .bold()
            .to_string()
        } else {
            log.level.clone()
        } + " ")
    } else {
        String::new()
    };

    let timestamp = if timestamps {
        (if colors {
            style(log.timestamp.to_rfc2822()).dim().to_string()
        } else {
            log.timestamp.to_rfc2822()
        } + " ")
    } else {
        String::new()
    };

    format!("{timestamp}{log_level}{}", log.message)
}

pub fn format_single_metrics(
    metrics: &Option<Metrics>,
    deployment: &Deployment,
) -> Result<Vec<String>> {
    let mut buff = vec![];

    buff.push(format!(
        "CPU: {}",
        metrics
            .clone()
            .map(|m| format!(
                "{:.2}%/{} vcpu",
                m.cpu_usage_percent(deployment.config.resources.vcpu),
                deployment.config.resources.vcpu
            ))
            .unwrap_or_else(|| UNAVAILABLE_ELEMENT.to_string())
    ));

    buff.push(format!(
        "Memory: {}",
        metrics
            .clone()
            .map(|m| -> Result<String> {
                let ram = parse_size(&deployment.config.resources.ram)?;

                Ok(format!(
                    "{:.2}% {}/{}",
                    m.memory_usage_percent(ram),
                    user_friendly_size(m.memory_usage_bytes)?,
                    user_friendly_size(ram)?
                ))
            })
            .transpose()?
            .unwrap_or_else(|| UNAVAILABLE_ELEMENT.to_string())
    ));

    Ok(buff)
}