feat: rely on DockerLike trait for container ops

This commit is contained in:
Aravinth Manivannan 2023-09-27 17:50:00 +05:30
parent 44a97e8928
commit 8ad1be03a8
Signed by: realaravinth
GPG key ID: F8F50389936984FF
4 changed files with 88 additions and 29 deletions

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
/* /*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net> * Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
* *
@ -14,13 +15,17 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::Arc; use std::sync::{Arc, RwLock};
use std::thread;
use reqwest::Client;
use tokio::sync::mpsc::Sender;
use crate::db::*; use crate::db::*;
use crate::docker::Docker;
use crate::docker::DockerLike;
use crate::settings::Settings; use crate::settings::Settings;
use reqwest::Client;
use tracing::info; use super::complaince::result::Result as CResult;
pub type ArcCtx = Arc<Ctx>; pub type ArcCtx = Arc<Ctx>;
@ -28,19 +33,23 @@ pub type ArcCtx = Arc<Ctx>;
pub struct Ctx { pub struct Ctx {
pub settings: Settings, pub settings: Settings,
pub db: Database, pub db: Database,
client: Client, pub client: Client,
pub results: Arc<RwLock<HashMap<String, Sender<CResult>>>>,
pub docker: Arc<dyn DockerLike>,
} }
impl Ctx { impl Ctx {
pub async fn new(settings: Settings) -> Arc<Self> { pub async fn new(settings: Settings) -> Arc<Self> {
let results = HashMap::default();
let results = Arc::new(RwLock::new(results));
let client = Client::default(); let client = Client::default();
let db = get_db(&settings).await; let db = get_db(&settings).await;
#[cfg(not(debug_assertions))]
init.join();
Arc::new(Self { Arc::new(Self {
settings, settings,
client, client,
db, db,
results,
docker: Arc::new(Docker::new()),
}) })
} }
} }

View file

@ -1,10 +1,33 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::process::Command; use std::process::Command;
#[derive(Default, Clone, Eq, PartialEq)]
pub struct Docker; pub struct Docker;
impl Docker { impl Docker {
pub fn version() -> String { 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") let version = Command::new("docker")
.arg("--version") .arg("--version")
.output() .output()
@ -14,7 +37,8 @@ impl Docker {
x.get(1).unwrap().trim().to_string() x.get(1).unwrap().trim().to_string()
} }
pub fn run_container( fn run_container(
&self,
name: &str, name: &str,
img: &str, img: &str,
detached: bool, detached: bool,
@ -51,7 +75,7 @@ impl Docker {
child.wait().unwrap(); child.wait().unwrap();
} }
pub fn get_exit_status(name: &str) -> isize { fn get_exit_status(&self, name: &str) -> isize {
let output = Command::new("docker") let output = Command::new("docker")
.args(["inspect", name, "--format={{.State.ExitCode}}"]) .args(["inspect", name, "--format={{.State.ExitCode}}"])
.output() .output()
@ -60,7 +84,7 @@ impl Docker {
let out = out.trim(); let out = out.trim();
out.parse::<isize>().unwrap() out.parse::<isize>().unwrap()
} }
pub fn get_logs(name: &str) -> String { fn get_logs(&self, name: &str) -> String {
let output = Command::new("docker") let output = Command::new("docker")
.args(["logs", name]) .args(["logs", name])
.output() .output()
@ -68,7 +92,7 @@ impl Docker {
String::from_utf8(output.stdout).unwrap() String::from_utf8(output.stdout).unwrap()
} }
pub fn rm_container(name: &str, force: bool) { fn rm_container(&self, name: &str, force: bool) {
let args = if force { let args = if force {
vec!["rm", name] vec!["rm", name]
} else { } else {
@ -81,6 +105,26 @@ impl Docker {
} }
} }
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)] #[cfg(test)]
mod tests { mod tests {
use crate::utils::get_random; use crate::utils::get_random;
@ -89,12 +133,13 @@ mod tests {
#[test] #[test]
fn test_docker_util() { fn test_docker_util() {
Docker::version(); let d = Docker::new();
d.version();
let name = format!("test_sleep__{}", get_random(4)); let name = format!("test_sleep__{}", get_random(4));
let mut env = HashMap::new(); let mut env = HashMap::new();
env.insert("FOO".to_string(), "BAR".to_string()); env.insert("FOO".to_string(), "BAR".to_string());
env.insert("BAZ".to_string(), "BOO".to_string()); env.insert("BAZ".to_string(), "BOO".to_string());
Docker::run_container( d.run_container(
&name, &name,
"forgeflux/ftest-dev-docker-cmd", "forgeflux/ftest-dev-docker-cmd",
true, true,
@ -109,12 +154,12 @@ mod tests {
let out = String::from_utf8(out.stdout).unwrap(); let out = String::from_utf8(out.stdout).unwrap();
assert!(out.contains("true")); assert!(out.contains("true"));
std::thread::sleep(std::time::Duration::new(10, 0)); std::thread::sleep(std::time::Duration::new(10, 0));
let logs = Docker::get_logs(&name); let logs = d.get_logs(&name);
println!("{logs}"); println!("{logs}");
assert!(logs.contains("running")); assert!(logs.contains("running"));
assert!(logs.contains("FOO=BAR")); assert!(logs.contains("FOO=BAR"));
assert!(logs.contains("BAZ=BOO")); assert!(logs.contains("BAZ=BOO"));
assert_eq!(Docker::get_exit_status(&name), 0); assert_eq!(d.get_exit_status(&name), 0);
Docker::rm_container(&name, true); d.rm_container(&name, true);
} }
} }

View file

@ -1,10 +1,12 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::sync::Arc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue; use serde_json::Value as JValue;
use crate::docker::Docker; use crate::docker::Docker;
use crate::docker::DockerLike;
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct Container { pub struct Container {
@ -15,11 +17,12 @@ pub struct Container {
pub struct DockerCompose { pub struct DockerCompose {
base_dir: PathBuf, base_dir: PathBuf,
docker: Arc<dyn DockerLike>,
} }
impl DockerCompose { impl DockerCompose {
pub fn new(base_dir: PathBuf) -> Self { pub fn new(base_dir: PathBuf, docker: Arc<dyn DockerLike>) -> Self {
Self { base_dir } Self { base_dir, docker }
} }
pub fn version() -> String { pub fn version() -> String {
let version = Command::new("docker-compose") let version = Command::new("docker-compose")
@ -67,7 +70,7 @@ impl DockerCompose {
} }
pub fn logs(&self, service: &str) -> String { pub fn logs(&self, service: &str) -> String {
Docker::get_logs(service) self.docker.get_logs(service)
} }
pub fn down(&self, remove_orphans: bool, volumes: bool) { pub fn down(&self, remove_orphans: bool, volumes: bool) {
@ -95,7 +98,7 @@ mod tests {
#[test] #[test]
fn test_docker_compose() { fn test_docker_compose() {
DockerCompose::version(); DockerCompose::version();
let cmp = DockerCompose::new("./tests/".into()); let cmp = DockerCompose::new("./tests/".into(), Arc::new(Docker::new()));
assert!(cmp.services().is_empty()); assert!(cmp.services().is_empty());
cmp.up(); cmp.up();
let services = cmp.services(); let services = cmp.services();

View file

@ -2,11 +2,13 @@ use std::collections::HashMap;
use crate::complaince::target::Target; use crate::complaince::target::Target;
use crate::docker::Docker; use crate::docker::Docker;
use crate::docker::DockerLike;
use crate::utils::get_random; use crate::utils::get_random;
use crate::AppCtx;
use super::results::{ArchivableContainer, ArchivableInitResult}; use super::results::{ArchivableContainer, ArchivableInitResult};
pub fn launch_init_containers(target: &Target) -> Option<Vec<ArchivableInitResult>> { pub fn launch_init_containers(ctx: &AppCtx, target: &Target) -> Option<Vec<ArchivableInitResult>> {
if let Some(init_scripts) = target.init_scripts.as_ref() { if let Some(init_scripts) = target.init_scripts.as_ref() {
let mut init_results = Vec::with_capacity(init_scripts.len()); let mut init_results = Vec::with_capacity(init_scripts.len());
for init in init_scripts.iter() { for init in init_scripts.iter() {
@ -17,7 +19,7 @@ pub fn launch_init_containers(target: &Target) -> Option<Vec<ArchivableInitResul
env.extend(custom_vars); env.extend(custom_vars);
} }
let name = format!("{}--{}", init.name, &auth[0..5]); let name = format!("{}--{}", init.name, &auth[0..5]);
Docker::run_container( ctx.docker.run_container(
&name, &name,
&init.container, &init.container,
false, false,
@ -25,9 +27,9 @@ pub fn launch_init_containers(target: &Target) -> Option<Vec<ArchivableInitResul
Some("ftest".to_string()), Some("ftest".to_string()),
true, true,
); );
let logs = Docker::get_logs(&name); let logs = ctx.docker.get_logs(&name);
let exit_code = Docker::get_exit_status(&name); let exit_code = ctx.docker.get_exit_status(&name);
Docker::rm_container(&name, true); ctx.docker.rm_container(&name, true);
let c = ArchivableInitResult { let c = ArchivableInitResult {
success: exit_code == 0, success: exit_code == 0,
exit_code, exit_code,
@ -56,8 +58,8 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn launch_init_containers_works() { async fn launch_init_containers_works() {
// let settings = Settings::new().unwrap(); let settings = Settings::new().unwrap();
// let ctx = AppCtx::new(Ctx::new(settings.clone()).await); let ctx = AppCtx::new(Ctx::new(settings.clone()).await);
// let base_dir = Path::new(&ctx.settings.repository.base_dir); // let base_dir = Path::new(&ctx.settings.repository.base_dir);
// let control = base_dir.join("control"); // let control = base_dir.join("control");
@ -84,7 +86,7 @@ mod tests {
suites: Vec::default(), suites: Vec::default(),
}; };
let init = launch_init_containers(&target); let init = launch_init_containers(&ctx, &target);
assert!(init.is_some()); assert!(init.is_some());
let init = init.unwrap(); let init = init.unwrap();
assert_eq!(init.len(), 1); assert_eq!(init.len(), 1);