use std::net::Ipv4Addr; use std::time::Duration; use models::{Error, ErrorKind, PackageId, ResultExt, Version}; use nix::sys::signal::Signal; use tokio::process::Command; use crate::util::Invoke; #[cfg(feature = "docker")] pub const CONTAINER_TOOL: &str = "docker"; #[cfg(not(feature = "docker"))] pub const CONTAINER_TOOL: &str = "podman"; #[cfg(feature = "docker")] pub const CONTAINER_DATADIR: &str = "/var/lib/docker"; #[cfg(not(feature = "docker"))] pub const CONTAINER_DATADIR: &str = "/var/lib/containers"; pub struct DockerImageSha(String); // docker images start9/${package}/*:${version} -q --no-trunc pub async fn images_for( package: &PackageId, version: &Version, ) -> Result, Error> { Ok(String::from_utf8( Command::new(CONTAINER_TOOL) .arg("images") .arg(format!("start9/{package}/*:{version}")) .arg("--no-trunc") .arg("-q") .invoke(ErrorKind::Docker) .await?, )? .lines() .map(|l| DockerImageSha(l.trim().to_owned())) .collect()) } // docker rmi -f ${sha} pub async fn remove_image(sha: &DockerImageSha) -> Result<(), Error> { match Command::new(CONTAINER_TOOL) .arg("rmi") .arg("-f") .arg(&sha.0) .invoke(ErrorKind::Docker) .await .map(|_| ()) { Err(e) if e.source .to_string() .to_ascii_lowercase() .contains("no such image") => { Ok(()) } a => a, }?; Ok(()) } // docker image prune -f pub async fn prune_images() -> Result<(), Error> { Command::new(CONTAINER_TOOL) .arg("image") .arg("prune") .arg("-f") .invoke(ErrorKind::Docker) .await?; Ok(()) } // docker container inspect ${name} --format '{{.NetworkSettings.Networks.start9.IPAddress}}' pub async fn get_container_ip(name: &str) -> Result, Error> { match Command::new(CONTAINER_TOOL) .arg("container") .arg("inspect") .arg(name) .arg("--format") .arg("{{.NetworkSettings.Networks.start9.IPAddress}}") .invoke(ErrorKind::Docker) .await { Err(e) if e.source .to_string() .to_ascii_lowercase() .contains("no such container") => { Ok(None) } Err(e) => Err(e), Ok(a) => { let out = std::str::from_utf8(&a)?.trim(); if out.is_empty() { Ok(None) } else { Ok(Some({ out.parse() .with_ctx(|_| (ErrorKind::ParseNetAddress, out.to_string()))? })) } } } } // docker stop -t ${timeout} -s ${signal} ${name} pub async fn stop_container( name: &str, timeout: Option, signal: Option, ) -> Result<(), Error> { let mut cmd = Command::new(CONTAINER_TOOL); cmd.arg("stop"); if let Some(dur) = timeout { cmd.arg("-t").arg(dur.as_secs().to_string()); } if let Some(sig) = signal { cmd.arg("-s").arg(sig.to_string()); } cmd.arg(name); match cmd.invoke(ErrorKind::Docker).await { Ok(_) => Ok(()), Err(mut e) if e.source .to_string() .to_ascii_lowercase() .contains("no such container") => { e.kind = ErrorKind::NotFound; Err(e) } Err(e) => Err(e), } } // docker kill -s ${signal} ${name} pub async fn kill_container(name: &str, signal: Option) -> Result<(), Error> { let mut cmd = Command::new(CONTAINER_TOOL); cmd.arg("kill"); if let Some(sig) = signal { cmd.arg("-s").arg(sig.to_string()); } cmd.arg(name); match cmd.invoke(ErrorKind::Docker).await { Ok(_) => Ok(()), Err(mut e) if e.source .to_string() .to_ascii_lowercase() .contains("no such container") => { e.kind = ErrorKind::NotFound; Err(e) } Err(e) => Err(e), } } // docker pause ${name} pub async fn pause_container(name: &str) -> Result<(), Error> { let mut cmd = Command::new(CONTAINER_TOOL); cmd.arg("pause"); cmd.arg(name); match cmd.invoke(ErrorKind::Docker).await { Ok(_) => Ok(()), Err(mut e) if e.source .to_string() .to_ascii_lowercase() .contains("no such container") => { e.kind = ErrorKind::NotFound; Err(e) } Err(e) => Err(e), } } // docker unpause ${name} pub async fn unpause_container(name: &str) -> Result<(), Error> { let mut cmd = Command::new(CONTAINER_TOOL); cmd.arg("unpause"); cmd.arg(name); match cmd.invoke(ErrorKind::Docker).await { Ok(_) => Ok(()), Err(mut e) if e.source .to_string() .to_ascii_lowercase() .contains("no such container") => { e.kind = ErrorKind::NotFound; Err(e) } Err(e) => Err(e), } } // docker rm -f ${name} pub async fn remove_container(name: &str, force: bool) -> Result<(), Error> { let mut cmd = Command::new(CONTAINER_TOOL); cmd.arg("rm"); if force { cmd.arg("-f"); } cmd.arg(name); match cmd.invoke(ErrorKind::Docker).await { Ok(_) => Ok(()), Err(e) if e.source .to_string() .to_ascii_lowercase() .contains("no such container") => { Ok(()) } Err(e) => Err(e), } } // docker network create -d bridge --subnet ${subnet} --opt com.podman.network.bridge.name=${bridge_name} pub async fn create_bridge_network( name: &str, subnet: &str, bridge_name: &str, ) -> Result<(), Error> { let mut cmd = Command::new(CONTAINER_TOOL); cmd.arg("network").arg("create"); cmd.arg("-d").arg("bridge"); cmd.arg("--subnet").arg(subnet); cmd.arg("--opt") .arg(format!("com.docker.network.bridge.name={bridge_name}")); cmd.arg(name); cmd.invoke(ErrorKind::Docker).await?; Ok(()) }