use crate::logging::{log_info, log_warn};
use crate::utils::command_exists;
use colored::Colorize;
use tokio::process::Command;
const COMMAND_NAME: &str = "docker";
pub async fn print_docker_ps(debug: bool) -> Result<(), String> {
if !command_exists("docker") {
if debug {
println!("{}", "Docker not detected; skipping docker ps.".dimmed());
}
return Ok(());
}
let has_sudo = command_exists("sudo");
let candidates = candidate_ps_commands(has_sudo);
let mut last_error: Option<String> = None;
for candidate in candidates {
let (binary, args) = candidate
.split_first()
.ok_or_else(|| "docker command args were empty".to_string())?;
let output = Command::new(binary)
.args(args)
.output()
.await
.map_err(|e| format!("Failed to run {}: {}", binary, e))?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
render_docker_table(&stdout);
let _ = log_info(COMMAND_NAME, "Rendered docker ps snapshot", None).await;
return Ok(());
} else {
last_error = Some(String::from_utf8_lossy(&output.stderr).to_string());
if debug {
println!(
"{} {}",
"docker ps failed:".yellow(),
last_error.as_deref().unwrap_or_default()
);
}
}
}
if let Some(err) = last_error {
let _ = log_warn(COMMAND_NAME, "Unable to render docker ps", Some(&err)).await;
}
Ok(())
}
fn render_docker_table(raw: &str) {
println!("\n{}", "Docker containers:".bright_blue());
let trimmed = raw.trim();
if trimmed.is_empty() {
println!("{}", "No running containers.".dimmed());
} else {
println!("{}", trimmed);
}
}
fn candidate_ps_commands(has_sudo: bool) -> Vec<Vec<String>> {
let base_format = "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}";
let mut cmds: Vec<Vec<String>> =
vec![vec!["docker".to_string(), "ps".to_string(), "--format".to_string(), base_format.to_string()]];
if has_sudo {
cmds.push(vec![
"sudo".to_string(),
"-n".to_string(),
"docker".to_string(),
"ps".to_string(),
"--format".to_string(),
base_format.to_string(),
]);
}
cmds
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_non_sudo_first() {
let cmds = candidate_ps_commands(true);
assert_eq!(cmds[0][0], "docker");
assert_eq!(cmds[0][1], "ps");
}
#[test]
fn sudo_variant_appended_when_available() {
let cmds = candidate_ps_commands(true);
assert!(cmds.iter().any(|c| c.first().map(|s| s.as_str()) == Some("sudo")));
}
#[test]
fn no_sudo_variant_when_not_available() {
let cmds = candidate_ps_commands(false);
assert!(cmds.iter().all(|c| c.first().map(|s| s.as_str()) != Some("sudo")));
}
#[test]
fn render_handles_empty_output() {
render_docker_table("");
}
}