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");
}
#[allow(
clippy::cast_precision_loss,
reason = "human-readable size display, not an exact value"
)]
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());
if let Some(health) = fetch_health() {
print_crontab_sync_warning(&health);
}
} else {
println!("moadim is not running");
}
Ok(liveness_exit_code(running))
}
fn print_crontab_sync_warning(health: &HealthInfo) {
let Some(sync) = health.crontab_sync.as_ref().filter(|sync| !sync.ok) else {
return;
};
let error = sync.last_error.as_deref().unwrap_or("unknown error");
println!("warning: routine OS crontab is stale: {error}");
println!("recovery: {CRONTAB_SYNC_RECOVERY_HINT}");
}
include!("status_json.rs");