use crate::config::Config;
use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, ClapArgs)]
pub struct Args {
#[arg(long)]
health: bool,
#[arg(long)]
jail: Option<String>,
#[arg(long)]
json: bool,
#[arg(short, long)]
quiet: bool,
}
#[derive(Debug, Serialize)]
struct JailStatus {
jail: String,
service: String,
jail_status: String,
service_status: String,
address: String,
health: String,
}
async fn check_jail_status(client: &reqwest::Client, ip: &str, port: Option<u16>) -> String {
if let Some(p) = port {
let addr = format!("{}:{}", ip, p);
let url = format!("http://{}", addr);
match client.get(&url).timeout(std::time::Duration::from_secs(3)).send().await {
Ok(_) => "up".to_string(),
Err(e) => {
if e.is_connect() || e.is_timeout() {
"down".to_string()
} else {
"unknown".to_string()
}
}
}
} else {
"unknown".to_string()
}
}
async fn check_service_status(
client: &reqwest::Client,
ip: &str,
port: u16,
health_path: &str,
) -> (String, String) {
let url = format!("http://{}:{}{}", ip, port, health_path);
match client.get(&url).timeout(std::time::Duration::from_secs(5)).send().await {
Ok(resp) => {
let status = if resp.status().is_success() {
"running".to_string()
} else {
format!("error ({})", resp.status())
};
let health = if resp.status().is_success() {
format!("ok ({})", resp.status())
} else {
format!("fail ({})", resp.status())
};
(status, health)
}
Err(e) => {
let status = if e.is_connect() {
"stopped".to_string()
} else if e.is_timeout() {
"timeout".to_string()
} else {
"unknown".to_string()
};
let health = if e.is_connect() {
"fail (connection refused)".to_string()
} else if e.is_timeout() {
"fail (timeout)".to_string()
} else {
format!("fail ({})", e)
};
(status, health)
}
}
}
pub async fn run(args: Args) -> Result<()> {
let config = Config::load().unwrap_or_default();
config.init_logging();
tracing::info!("Checking jail status");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()?;
let jails = vec![
("rmpca-extract", "extract", "10.10.0.2", Some(4000), Some("/")),
("rmpca-backend", "backend", "10.10.0.3", Some(3000), Some("/health")),
("rmpca-optimizer", "optimizer", "10.10.0.5", Some(8000), Some("/health")),
("rmpca-nginx-opt", "nginx", "10.10.0.7", Some(80), Some("/health")),
];
let mut results = Vec::new();
for (jail, service, ip, port, health_path) in jails {
if let Some(ref filter) = args.jail {
if jail != filter && service != filter {
continue;
}
}
let address = match port {
Some(p) => format!("{}:{}", ip, p),
None => ip.to_string(),
};
let jail_status = check_jail_status(&client, ip, port).await;
let (service_status, health_status) = if args.health && port.is_some() && health_path.is_some() {
check_service_status(&client, ip, port.unwrap(), health_path.unwrap()).await
} else if port.is_some() {
match check_jail_status(&client, ip, port).await.as_str() {
"up" => ("running".to_string(), "–".to_string()),
"down" => ("stopped".to_string(), "–".to_string()),
_ => ("unknown".to_string(), "–".to_string()),
}
} else {
("unknown".to_string(), "–".to_string())
};
results.push(JailStatus {
jail: jail.to_string(),
service: service.to_string(),
jail_status,
service_status,
address,
health: health_status,
});
}
if args.json {
println!("{}", serde_json::to_string_pretty(&results)?);
} else if args.quiet {
for r in &results {
println!("{} {} {}", r.jail, r.jail_status, r.service_status);
}
} else {
println!("{:<24} {:<10} {:<10} {:<14} {}",
"JAIL", "JAIL", "SERVICE", "IP:PORT", "HEALTH");
println!("{}", "-".repeat(78));
for r in &results {
println!("{:<24} {:<10} {:<10} {:<14} {}",
r.jail, r.jail_status, r.service_status, r.address, r.health);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_args() {
let args = Args {
health: true,
jail: Some("rmpca-backend".to_string()),
json: false,
quiet: true,
};
assert!(args.health);
assert_eq!(args.jail, Some("rmpca-backend".to_string()));
assert!(args.quiet);
}
}