ftest/src/docker.rs

170 lines
4.6 KiB
Rust

// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::collections::HashMap;
use std::process::Command;
#[derive(Default, Clone, Eq, PartialEq)]
pub struct Docker;
impl Docker {
pub fn new() -> Self {
Docker
}
}
pub trait DockerLike: std::marker::Send + std::marker::Sync + CloneDockerLike {
fn version(&self) -> String;
fn run_container(
&self,
name: &str,
img: &str,
detached: bool,
env: &HashMap<String, String>,
network: Option<String>,
pull: bool,
);
fn get_exit_status(&self, name: &str) -> isize;
fn get_logs(&self, name: &str) -> String;
fn rm_container(&self, name: &str, force: bool);
}
impl DockerLike for Docker {
fn version(&self) -> String {
let version = Command::new("docker")
.arg("--version")
.output()
.expect("unable to obtain Docker version");
let x = String::from_utf8(version.stdout).unwrap();
let x: Vec<&str> = x.split("Docker version ").collect();
x.get(1).unwrap().trim().to_string()
}
fn run_container(
&self,
name: &str,
img: &str,
detached: bool,
env: &HashMap<String, String>,
network: Option<String>,
pull: bool,
) {
let mut env_args = Vec::with_capacity(env.len() * 2 + 6);
env_args.push("run".to_string());
if detached {
env_args.push("-d".to_string());
}
if pull {
env_args.push("--pull=always".into());
}
env_args.push("--name".to_string());
env_args.push(name.to_string());
for (k, v) in env.iter() {
env_args.push("-e".to_string());
env_args.push(format!("{k}={v}"));
}
if let Some(network) = network {
env_args.push("--network".into());
env_args.push(network);
}
env_args.push(img.to_string());
let mut child = Command::new("docker")
.args(&env_args)
.spawn()
.expect("unable to obtain Docker version");
child.wait().unwrap();
}
fn get_exit_status(&self, name: &str) -> isize {
let output = Command::new("docker")
.args(["inspect", name, "--format={{.State.ExitCode}}"])
.output()
.expect("unable to exit status");
let out = String::from_utf8(output.stdout).unwrap();
let out = out.trim();
out.parse::<isize>().unwrap()
}
fn get_logs(&self, name: &str) -> String {
let output = Command::new("docker")
.args(["logs", name])
.output()
.expect("unable to get logs");
String::from_utf8(output.stdout).unwrap()
}
fn rm_container(&self, name: &str, force: bool) {
let args = if force {
vec!["rm", name]
} else {
vec!["rm", "--force", name]
};
Command::new("docker")
.args(args)
.spawn()
.expect(&format!("unable to remove docker container {name}"));
}
}
pub trait CloneDockerLike {
/// clone DB
fn clone_docker_like(&self) -> Box<dyn DockerLike>;
}
impl<T> CloneDockerLike for T
where
T: DockerLike + Clone + 'static,
{
fn clone_docker_like(&self) -> Box<dyn DockerLike> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn DockerLike> {
fn clone(&self) -> Self {
(**self).clone_docker_like()
}
}
#[cfg(test)]
mod tests {
use crate::utils::get_random;
use super::*;
#[test]
fn test_docker_util() {
let d = Docker::new();
d.version();
let name = format!("test_sleep__{}", get_random(4));
let mut env = HashMap::new();
env.insert("FOO".to_string(), "BAR".to_string());
env.insert("BAZ".to_string(), "BOO".to_string());
d.run_container(
&name,
"forgeflux/ftest-docker-cmd-tester",
true,
&env,
None,
true,
);
let out = Command::new("docker")
.args(["container", "inspect", "-f", "'{{.State.Running}}'", &name])
.output()
.unwrap();
let out = String::from_utf8(out.stdout).unwrap();
assert!(out.contains("true"));
std::thread::sleep(std::time::Duration::new(10, 0));
let logs = d.get_logs(&name);
println!("{logs}");
assert!(logs.contains("running"));
assert!(logs.contains("FOO=BAR"));
assert!(logs.contains("BAZ=BOO"));
assert_eq!(d.get_exit_status(&name), 0);
d.rm_container(&name, true);
}
}