use std::path::{Path, PathBuf};
use isoloom_manifest::Tool;
use crate::Result;
use crate::executor::Cx;
use crate::process::ProcessSpec;
pub(crate) fn tool_for<'a>(cx: &'a Cx<'_>, program: &str) -> Option<&'a Tool> {
let host = cx.run.runner.config.host;
cx.run
.manifest()
.tools
.iter()
.find(|t| t.container.is_some() && t.container_hosts.contains(&host) && program_of(t).is_some_and(|p| p == program))
}
fn program_of(tool: &Tool) -> Option<&str> {
match &tool.probe {
isoloom_manifest::HostArgv::Every(a) => Some(a.program().as_str()),
isoloom_manifest::HostArgv::PerHost(p) => p.default.as_ref().map(|a| a.program().as_str()),
}
}
pub(crate) fn wrap(cx: &Cx<'_>, spec: ProcessSpec) -> Result<ProcessSpec> {
let Some(image) = tool_for(cx, &spec.program).and_then(|t| t.container.as_ref()) else {
return Ok(spec);
};
let state = cx.run.runner.store.run_dir(cx.run.name())?;
let home = &cx.run.runner.config.home;
let mounts = mounts(&[lab_root(cx.run.dir()), &state, &home.join("ssh"), &crate::driver::plugin_cache(home)]);
Ok(docker_run(image.as_str(), &mounts, spec))
}
pub(crate) fn lab_root(dir: &Path) -> &Path {
dir.ancestors()
.find(|p| p.file_name().is_some_and(|n| n.to_string_lossy().starts_with(".isoloom")))
.and_then(Path::parent)
.unwrap_or(dir)
}
fn mounts(folders: &[&Path]) -> Vec<PathBuf> {
let mut all: Vec<PathBuf> = folders.iter().map(|p| p.to_path_buf()).collect();
all.sort();
all.dedup();
let outer = all.clone();
all.retain(|p| !outer.iter().any(|o| o != p && p.starts_with(o)));
all
}
fn docker_run(image: &str, mounts: &[PathBuf], spec: ProcessSpec) -> ProcessSpec {
let mut args: Vec<String> = vec!["run".into(), "--rm".into(), "-i".into()];
if let Some(cwd) = &spec.cwd {
args.extend(["-w".into(), cwd.display().to_string()]);
}
for m in mounts {
let m = m.display().to_string();
args.extend(["-v".into(), format!("{m}:{m}")]);
}
for (name, _) in &spec.env {
args.extend(["-e".into(), name.clone()]);
}
args.push(image.to_owned());
args.extend(spec.args);
ProcessSpec {
program: "docker".into(),
args,
cwd: spec.cwd,
env: spec.env,
stdin: spec.stdin,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_lab_root_is_above_its_isoloom_folder() {
assert_eq!(lab_root(Path::new("/l/x/.isoloom/proxmox")), Path::new("/l/x"));
assert_eq!(lab_root(Path::new("/l/x/.isoloom-2/docker-vm/proxmox")), Path::new("/l/x"));
assert_eq!(lab_root(Path::new("/tmp/m")), Path::new("/tmp/m"));
}
#[test]
fn a_folder_inside_another_mount_isnt_mounted_twice() {
let m = mounts(&[Path::new("/h/runs/r"), Path::new("/h"), Path::new("/l"), Path::new("/h/ssh"), Path::new("/l")]);
assert_eq!(m, [PathBuf::from("/h"), PathBuf::from("/l")]);
}
#[test]
fn the_container_gets_the_variables_by_name_never_their_values() {
let spec = ProcessSpec {
program: "terraform".into(),
args: vec!["apply".into(), "-auto-approve".into()],
cwd: Some("/l/x/.isoloom/proxmox".into()),
env: vec![("TF_VAR_proxmox_password".into(), "s3cret".into())],
stdin: None,
};
let run = docker_run("hashicorp/terraform:1.9.8@sha256:00", &[PathBuf::from("/l/x"), PathBuf::from("/h")], spec);
assert_eq!(run.program, "docker");
assert_eq!(
run.args,
[
"run",
"--rm",
"-i",
"-w",
"/l/x/.isoloom/proxmox",
"-v",
"/l/x:/l/x",
"-v",
"/h:/h",
"-e",
"TF_VAR_proxmox_password",
"hashicorp/terraform:1.9.8@sha256:00",
"apply",
"-auto-approve"
]
);
assert!(!run.args.iter().any(|a| a.contains("s3cret")));
assert_eq!(run.env, [("TF_VAR_proxmox_password".to_owned(), "s3cret".to_owned())]);
}
}