use super::{
bind_addr, http_request, http_request_json, http_request_with_body, is_running,
liveness_exit_code, parse_freed_bytes, parse_removed_count, read_pid_file, wait_until,
Duration,
};
pub fn cleanup(json: bool) -> anyhow::Result<i32> {
match http_request_with_body("POST", "/api/v1/routines/cleanup") {
Ok((200, body)) => {
let removed = parse_removed_count(&body).unwrap_or(0);
let freed_bytes = parse_freed_bytes(&body).unwrap_or(0);
if json {
println!("{}", cleanup_json(removed, freed_bytes, true));
} else {
let plural = if removed == 1 { "" } else { "es" };
println!(
"cleanup removed {removed} workbench{plural} (freed {})",
humanize_bytes(freed_bytes)
);
}
Ok(liveness_exit_code(true))
}
Ok((status, _)) => {
anyhow::bail!("unexpected response from server: HTTP {status}");
}
Err(_) => {
if json {
println!("{}", cleanup_json(0, 0, false));
} else {
println!("moadim is not running");
}
Ok(liveness_exit_code(false))
}
}
}
pub(super) fn humanize_bytes(bytes: u64) -> String {
const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
if bytes < 1024 {
return format!("{bytes} B");
}
let mut size = bytes as f64;
let mut unit = 0;
while size >= 1024.0 && unit < UNITS.len() - 1 {
size /= 1024.0;
unit += 1;
}
format!("{size:.1} {}", UNITS[unit])
}
pub fn trigger(id: &str) -> anyhow::Result<i32> {
match http_request("POST", &format!("/api/v1/routines/{id}/trigger")) {
Ok(200) => {
println!("triggered routine {id}");
Ok(liveness_exit_code(true))
}
Ok(404) => {
anyhow::bail!("no routine with id {id}");
}
Ok(status) => {
anyhow::bail!("unexpected response from server: HTTP {status}");
}
Err(_) => {
println!("moadim is not running");
Ok(liveness_exit_code(false))
}
}
}
pub fn logs(id: &str) -> anyhow::Result<i32> {
match http_request_json("GET", &format!("/api/v1/routines/{id}/logs"), None) {
Ok((200, body)) => {
if !body.is_empty() {
println!("{body}");
}
Ok(liveness_exit_code(true))
}
Ok((404, _)) => {
anyhow::bail!("no routine with id {id}");
}
Ok((status, _)) => {
anyhow::bail!("unexpected response from server: HTTP {status}");
}
Err(_) => {
println!("moadim is not running");
Ok(liveness_exit_code(false))
}
}
}
pub fn status(json: bool, wait_secs: Option<u64>) -> anyhow::Result<i32> {
let mut running = is_running();
if !running {
if let Some(secs) = wait_secs {
running = wait_until(is_running, Duration::from_secs(secs));
}
}
let pid = read_pid_file();
if json {
let health = if running { fetch_health() } else { None };
println!("{}", status_json(running, pid, health.as_ref()));
return Ok(liveness_exit_code(running));
}
if running {
let pid_suffix = pid
.map(|process_id| format!(" (pid {process_id})"))
.unwrap_or_default();
println!("moadim is running{pid_suffix} at http://{}", bind_addr());
} else {
println!("moadim is not running");
}
Ok(liveness_exit_code(running))
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct HealthInfo {
pub(super) uptime_secs: u64,
pub(super) version: String,
}
pub(super) fn status_json(running: bool, pid: Option<u32>, health: Option<&HealthInfo>) -> String {
let uptime_secs = health.map(|info| info.uptime_secs);
let version = health.map(|info| info.version.as_str());
serde_json::json!({
"running": running,
"pid": pid,
"address": bind_addr(),
"uptime_secs": uptime_secs,
"version": version,
})
.to_string()
}
pub(super) fn fetch_health() -> Option<HealthInfo> {
let (status, body) = http_request_with_body("GET", "/api/v1/health").ok()?;
(status == 200).then(|| parse_health(&body)).flatten()
}
pub(super) fn parse_health(body: &str) -> Option<HealthInfo> {
let value: serde_json::Value = serde_json::from_str(body).ok()?;
let uptime_secs = value.get("uptime_secs")?.as_u64()?;
let version = value.get("version")?.as_str()?.to_string();
Some(HealthInfo {
uptime_secs,
version,
})
}
pub(super) fn cleanup_json(removed: usize, freed_bytes: u64, running: bool) -> String {
serde_json::json!({
"running": running,
"removed": removed,
"freed_bytes": freed_bytes,
"address": bind_addr(),
})
.to_string()
}