use crate::{bakefile::Task, docker};
use colored::Colorize;
use std::{
collections::{HashMap, HashSet},
io::Read,
path::Path,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
pub fn run<R: Read>(
task: &Task,
from_image: &str,
to_image: &str,
environment: &HashMap<String, String>,
mut tar: R,
bakefile_dir: &Path,
cache_hit: bool,
running: &Arc<AtomicBool>,
active_containers: &Arc<Mutex<HashSet<String>>>,
) -> Result<(), String> {
if cache_hit {
if task.output_paths.is_empty() {
return Ok(());
}
let container = docker::create_container(to_image, "true", running)?;
defer! {{
if let Err(e) = docker::delete_container(&container, running) {
error!("{}", e);
}
}};
let mut output_paths = task.output_paths.clone();
output_paths.sort();
docker::copy_from_container(
&container,
output_paths.as_ref(),
&task.location,
bakefile_dir,
running,
)?;
} else {
let mut commands_to_run = vec![];
commands_to_run.push(format!(
"mkdir -p {}",
shell_escape(&task.location.to_string_lossy())
));
commands_to_run.push(format!(
"chmod 777 {}",
shell_escape(&task.location.to_string_lossy())
));
if let Some(command) = &task.command {
commands_to_run.push(format!(
"cd {}",
shell_escape(&task.location.to_string_lossy())
));
for variable in task.environment.keys() {
commands_to_run.push(format!(
"export {}={}",
shell_escape(variable),
shell_escape(&environment[variable]), ));
}
commands_to_run.push(format!(
"su -c {} {}",
shell_escape(&command),
shell_escape(&task.user)
));
eprintln!("{}", command.blue());
}
let container = docker::create_container(
from_image,
&commands_to_run.join(" && "),
running,
)?;
{
active_containers.lock().unwrap().insert(container.clone());
}
defer! {{
{
active_containers.lock().unwrap().remove(&container);
}
if let Err(e) = docker::delete_container(&container, running) {
error!("{}", e);
}
}};
if !task.input_paths.is_empty() {
docker::copy_into_container(&container, &mut tar, running)?;
}
docker::start_container(&container, running).map_err(|_| {
if running.load(Ordering::SeqCst) {
"Task failed."
} else {
super::INTERRUPT_MESSAGE
}
.to_owned()
})?;
if !task.output_paths.is_empty() {
let mut output_paths = task.output_paths.clone();
output_paths.sort();
docker::copy_from_container(
&container,
output_paths.as_ref(),
&task.location,
bakefile_dir,
running,
)?;
}
docker::commit_container(&container, to_image, running)?;
}
Ok(())
}
fn shell_escape(command: &str) -> String {
format!("'{}'", command.replace("'", "'\\''"))
}
#[cfg(test)]
mod tests {
use crate::runner::shell_escape;
#[test]
fn shell_escape_empty() {
assert_eq!(shell_escape(""), "''");
}
#[test]
fn shell_escape_word() {
assert_eq!(shell_escape("foo"), "'foo'");
}
#[test]
fn shell_escape_single_quote() {
assert_eq!(shell_escape("f'o'o"), "'f'\\''o'\\''o'");
}
}