use std::path::Path;
use std::result::Result;
use std::ffi::{CString, OsStr};
use std::collections::HashMap;
use ymlctx::context::{Context, CtxObj};
use regex::Regex;
use nix::unistd::{fork, execvp, ForkResult};
use nix::sys::wait::{waitpid, WaitStatus};
use crate::JobError;
#[cfg(feature = "spawner_python")]
use pyo3::prelude::*;
#[cfg(feature = "spawner_python")]
use pyo3::types::PyList;
#[cfg(feature = "spawner_python")]
pub fn invoke_py(src: Context, ctx_step: Context) -> Result<(), JobError> {
let gil = Python::acquire_gil();
let py = gil.python();
let syspath: &PyList = py.import("sys").unwrap().get("path").unwrap().try_into().unwrap();
let ref src_path: String = src.unpack("src").unwrap();
let mod_path;
if let Some(parent) = Path::new(src_path).parent() {
mod_path = parent;
}
else {
mod_path = Path::new(".");
}
syspath.insert(0, mod_path.to_str().unwrap()).unwrap();
let mod_name;
if let Some(stem) = Path::new(src_path).file_stem() {
mod_name = stem.to_str().unwrap();
}
else {
unreachable!();
}
let mod_py = py.import(mod_name).unwrap();
let ref action: String = ctx_step.unpack("action").unwrap();
match mod_py.call_method1(action, (ctx_step.to_object(py), )) {
Ok(_) => Ok(()),
Err(e) => {
e.print(py);
Err(JobError {})
}
}
}
fn copy_user_info(facts: &mut HashMap<String, String>, user: &str) {
if let Some(output) = std::process::Command::new("getent").args(&["passwd", &user]).output().ok() {
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let fields: Vec<&str> = stdout.split(":").collect();
facts.insert(String::from("uid"), String::from(fields[2]));
facts.insert(String::from("gid"), String::from(fields[3]));
facts.insert(String::from("full_name"), String::from(fields[4]));
facts.insert(String::from("home_dir"), String::from(fields[5]));
}
}
pub fn format_cmd<I>(cmd: I) -> String
where I: IntoIterator<Item = String>
{
cmd.into_iter().map(|s| { if s.contains(" ") { format!("\"{}\"", s) } else { s.to_owned() } }).collect::<Vec<String>>().join(" ")
}
pub fn docker_start<I, S>(ctx_docker: Context, cmd: I) -> Result<(), JobError>
where I: IntoIterator<Item = S>, S: AsRef<OsStr>
{
let username;
let output = std::process::Command::new("id").output().unwrap();
let mut stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let newline_len = stdout.trim_right().len();
stdout.truncate(newline_len);
let rule = Regex::new(r"^uid=(?P<uid>[0-9]+)(\((?P<user>\w+)\))? gid=(?P<gid>[0-9]+)(\((?P<group>\w+)\))?").unwrap();
if let Some(caps) = rule.captures(&stdout) {
username = caps.name("user").unwrap().as_str().to_owned();
}
else { return Err(JobError {}); }
let mut userinfo = HashMap::new();
copy_user_info(&mut userinfo, &username);
let home = format!("/home/{}", &username);
let mut docker_run: Vec<String> = ["docker", "run", "--rm", "-t", "--net=host"].iter().map(|&s| {s.to_owned()}).collect();
docker_run.push(String::from("-v"));
docker_run.push(format!("{}:{}/current-ro:ro", std::env::current_dir().unwrap().to_str().unwrap(), &home));
docker_run.push(String::from("-w"));
docker_run.push(format!("{}/current-ro", &home));
docker_run.push(String::from("-e"));
docker_run.push(format!("TKSTACK_USER={}", &stdout));
if let Some(CtxObj::Str(runtime)) = ctx_docker.get("runtime") {
docker_run.push(format!("--runtime={}", runtime));
}
if let Some(CtxObj::Bool(interactive)) = ctx_docker.get("interactive") {
if *interactive {
docker_run.push(String::from("-i"));
}
}
if let Some(CtxObj::Array(volumes)) = ctx_docker.get("volumes") {
for v in volumes {
if let CtxObj::Str(vol) = v {
if let Some(i) = vol.find(":") {
let (src, dst) = vol.split_at(i);
let suffix = if dst.ends_with(":ro") || dst.ends_with(":rw") || dst.ends_with(":z") || dst.ends_with(":Z") { "" } else { ":ro" };
if let Ok(src) = Path::new(src).canonicalize() {
docker_run.push(String::from("-v"));
docker_run.push(format!("{}:{}{}", src.to_str().unwrap(), dst, suffix));
}
}
}
}
}
if let Some(CtxObj::Array(ports)) = ctx_docker.get("ports") {
for p in ports {
if let CtxObj::Str(port_map) = p {
docker_run.push(String::from("-p"));
docker_run.push(port_map.to_owned());
}
}
}
if let Some(CtxObj::Bool(gui)) = ctx_docker.get("gui") {
if *gui {
docker_run.extend::<Vec<String>>([
"-e", "DISPLAY", "-v", "/tmp/.X11-unix:/tmp/.X11-unix:rw",
"-v", &format!("{}/.Xauthority:{}/.Xauthority:ro", userinfo["home_dir"], home),
].iter().map(|&s| {s.to_owned()}).collect());
}
}
if let Some(CtxObj::Array(envs)) = ctx_docker.get("environment") {
for v in envs {
if let CtxObj::Str(var) = v {
docker_run.push(String::from("-e"));
docker_run.push(var.to_owned());
}
}
}
if let Some(CtxObj::Str(container_name)) = ctx_docker.get("container_name") {
docker_run.push(format!("--name={}", container_name));
}
if let Some(CtxObj::Str(image_name)) = ctx_docker.get("image") {
docker_run.push(image_name.to_owned());
}
else { return Err(JobError {}); }
docker_run.extend::<Vec<String>>(cmd.into_iter().map(|s| {s.as_ref().to_str().unwrap().to_owned()}).collect());
info!("{}", format_cmd(docker_run.clone()));
let docker_linux: Vec<CString> = docker_run.iter().map(|s| {CString::new(s as &str).unwrap()}).collect();
match fork() {
Ok(ForkResult::Child) => {
match execvp(&CString::new("docker").unwrap(), &docker_linux) {
Ok(_) => Ok(()),
Err(_) => Err(JobError {}),
}
},
Ok(ForkResult::Parent { child, .. }) => {
match waitpid(child, None) {
Ok(status) => match status {
WaitStatus::Exited(_, exit_code) => {
if exit_code == 0 { Ok(()) }
else { Err(JobError {}) } },
WaitStatus::Signaled(_, _sig, _core_dump) => {
Err(JobError {}) },
WaitStatus::Stopped(_, _sig) => unreachable!(),
WaitStatus::PtraceEvent(..) => unimplemented!(),
WaitStatus::PtraceSyscall(_) => unimplemented!(),
WaitStatus::Continued(_) => unreachable!(),
WaitStatus::StillAlive => unreachable!()
},
Err(_) => Err(JobError {})
}
},
Err(_) => Err(JobError {}),
}
}