use anyhow::{Context, Result};
use std::process::Command;
fn run_compose(compose_file: Option<&str>, subcommand: &str, service: Option<&str>, fail_context: &str) -> Result<()> {
let mut args = vec!["compose"];
if let Some(file) = compose_file {
args.push("-f");
args.push(file);
}
args.push(subcommand);
if let Some(svc) = service {
args.push(svc);
}
let status = Command::new("docker")
.args(&args)
.status()
.with_context(|| format!("Failed to execute 'docker compose {}'", subcommand))?;
if !status.success() {
return Err(anyhow::anyhow!("{}", fail_context));
}
Ok(())
}
pub fn compose_build(compose_file: Option<&str>, service: Option<&str>) -> Result<()> {
run_compose(compose_file, "build", service, "Failed to build Docker images")
}
fn run_compose_up(compose_file: Option<&str>, service: Option<&str>, detach: bool, fail_context: &str) -> Result<()> {
let args = compose_up_args(compose_file, service, detach);
let status = Command::new("docker")
.args(&args)
.status()
.with_context(|| "Failed to execute 'docker compose up'".to_string())?;
if !status.success() {
return Err(anyhow::anyhow!("{}", fail_context));
}
Ok(())
}
fn compose_up_args<'a>(compose_file: Option<&'a str>, service: Option<&'a str>, detach: bool) -> Vec<&'a str> {
let mut args = vec!["compose"];
if let Some(file) = compose_file {
args.push("-f");
args.push(file);
}
args.push("up");
if detach {
args.push("-d");
}
if let Some(svc) = service {
args.push(svc);
}
args
}
pub async fn compose_up(compose_file: Option<&str>, detach: bool) -> Result<()> {
run_compose_up(compose_file, None, detach, "Failed to start services")
}
pub async fn compose_up_service(compose_file: Option<&str>, service: &str, detach: bool) -> Result<()> {
run_compose_up(
compose_file,
Some(service),
detach,
&format!("Failed to start service: {}", service),
)
}
pub async fn compose_run_service(
compose_file: Option<&str>,
service: &str,
detach: bool,
service_ports: bool,
command_args: &[String],
) -> Result<()> {
let mut args = vec!["compose"];
if let Some(file) = compose_file {
args.push("-f");
args.push(file);
}
args.push("run");
if detach {
args.push("-d");
}
if service_ports {
args.push("--service-ports");
}
if !detach {
args.push("--rm");
}
args.push(service);
let cmd_args: Vec<&str> = command_args.iter().map(|s| s.as_str()).collect();
args.extend(cmd_args);
let status = Command::new("docker")
.args(&args)
.status()
.context(format!("Failed to run service: {}", service))?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to run service: {}", service));
}
Ok(())
}
pub async fn compose_stop(compose_file: Option<&str>, service: Option<&str>) -> Result<()> {
run_compose(compose_file, "stop", service, "Failed to stop services")
}
pub async fn compose_down(compose_file: Option<&str>) -> Result<()> {
run_compose(compose_file, "down", None, "Failed to stop and remove services")
}
pub async fn compose_restart(compose_file: Option<&str>, service: Option<&str>) -> Result<()> {
run_compose(compose_file, "restart", service, "Failed to restart services")
}
pub fn compose_logs(
compose_file: Option<&str>,
service: Option<&str>,
follow: bool,
tail: Option<usize>,
) -> Result<()> {
let mut cmd = Command::new("docker");
cmd.arg("compose");
if let Some(file) = compose_file {
cmd.arg("-f").arg(file);
}
cmd.arg("logs");
if follow {
cmd.arg("-f");
}
if let Some(n) = tail {
cmd.arg("--tail").arg(n.to_string());
}
if let Some(svc) = service {
cmd.arg(svc);
}
let status = cmd.status().context("Failed to execute 'docker compose logs'")?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to get logs"));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compose_up_args_defaults_to_all_services_in_foreground() {
assert_eq!(compose_up_args(None, None, false), vec!["compose", "up"]);
}
#[test]
fn compose_up_args_adds_detach_flag_before_service() {
assert_eq!(
compose_up_args(None, Some("redis"), true),
vec!["compose", "up", "-d", "redis"]
);
}
#[test]
fn compose_up_args_places_compose_file_flag_before_the_subcommand() {
assert_eq!(
compose_up_args(Some("docker-compose.dev.yml"), None, false),
vec!["compose", "-f", "docker-compose.dev.yml", "up"]
);
}
}