240 lines
6.2 KiB
Rust
240 lines
6.2 KiB
Rust
|
|
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<Vec<DockerImageSha>, 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<Option<Ipv4Addr>, 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<Duration>,
|
||
|
|
signal: Option<Signal>,
|
||
|
|
) -> 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<Signal>) -> 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(())
|
||
|
|
}
|