mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 00:03:02 +00:00
90ad56d582
The cancellation informations where moved from bottom to top where the infromations are already set within the queue table.
794 lines
25 KiB
Rust
794 lines
25 KiB
Rust
use std::{collections::HashMap, fs, process::Stdio};
|
|
|
|
#[cfg(feature = "dind")]
|
|
use bollard::container::{
|
|
KillContainerOptions, RemoveContainerOptions, StatsOptions, StopContainerOptions,
|
|
};
|
|
#[cfg(feature = "dind")]
|
|
use futures::{stream, StreamExt, TryStreamExt};
|
|
use regex::Regex;
|
|
use serde_json::{json, value::RawValue};
|
|
use sqlx::types::Json;
|
|
use tokio::process::Command;
|
|
|
|
#[cfg(feature = "dind")]
|
|
use uuid::Uuid;
|
|
use windmill_common::{
|
|
error::Error,
|
|
jobs::QueuedJob,
|
|
worker::{to_raw_value, write_file},
|
|
};
|
|
|
|
#[cfg(feature = "dind")]
|
|
use windmill_common::DB;
|
|
|
|
#[cfg(feature = "dind")]
|
|
use windmill_common::error::to_anyhow;
|
|
|
|
use windmill_queue::append_logs;
|
|
|
|
lazy_static::lazy_static! {
|
|
pub static ref BIN_BASH: String = std::env::var("BASH_PATH").unwrap_or_else(|_| "/bin/bash".to_string());
|
|
}
|
|
const NSJAIL_CONFIG_RUN_BASH_CONTENT: &str = include_str!("../nsjail/run.bash.config.proto");
|
|
const NSJAIL_CONFIG_RUN_POWERSHELL_CONTENT: &str =
|
|
include_str!("../nsjail/run.powershell.config.proto");
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref RE_POWERSHELL_IMPORTS: Regex = Regex::new(r#"^Import-Module\s+(?:-Name\s+)?"?([^-\s"]+)"?"#).unwrap();
|
|
}
|
|
|
|
#[cfg(feature = "dind")]
|
|
use crate::handle_child::run_future_with_polling_update_job_poller;
|
|
|
|
use crate::{
|
|
common::{
|
|
build_args_map, get_reserved_variables, read_file, read_file_content, start_child_process,
|
|
OccupancyMetrics,
|
|
},
|
|
handle_child::handle_child,
|
|
AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
|
|
POWERSHELL_CACHE_DIR, POWERSHELL_PATH, PROXY_ENVS, TZ_ENV,
|
|
};
|
|
|
|
#[cfg(windows)]
|
|
use crate::SYSTEM_ROOT;
|
|
|
|
lazy_static::lazy_static! {
|
|
|
|
pub static ref ANSI_ESCAPE_RE: Regex = Regex::new(r"\x1b\[[0-9;]*m").unwrap();
|
|
}
|
|
|
|
#[tracing::instrument(level = "trace", skip_all)]
|
|
pub async fn handle_bash_job(
|
|
mem_peak: &mut i32,
|
|
job: &QueuedJob,
|
|
db: &sqlx::Pool<sqlx::Postgres>,
|
|
client: &AuthedClientBackgroundTask,
|
|
content: &str,
|
|
job_dir: &str,
|
|
shared_mount: &str,
|
|
base_internal_url: &str,
|
|
worker_name: &str,
|
|
envs: HashMap<String, String>,
|
|
occupancy_metrics: &mut OccupancyMetrics,
|
|
_killpill_rx: &mut tokio::sync::broadcast::Receiver<()>,
|
|
) -> Result<Box<RawValue>, Error> {
|
|
let annotation = windmill_common::worker::BashAnnotations::parse(&content);
|
|
|
|
let mut logs1 = "\n\n--- BASH CODE EXECUTION ---\n".to_string();
|
|
if annotation.docker {
|
|
logs1.push_str("docker mode\n");
|
|
}
|
|
append_logs(&job.id, &job.workspace_id, logs1, db).await;
|
|
|
|
write_file(job_dir, "main.sh", &format!("set -e\n{content}"))?;
|
|
let script = format!(
|
|
r#"
|
|
set -o pipefail
|
|
set -e
|
|
|
|
# Function to kill child processes
|
|
cleanup() {{
|
|
echo "Terminating child processes..."
|
|
|
|
# Ignore SIGTERM and SIGINT
|
|
trap '' SIGTERM SIGINT
|
|
|
|
# Kill the process group of the script (negative PID value)
|
|
pkill -P $$
|
|
exit
|
|
}}
|
|
|
|
|
|
# Trap SIGTERM (or other signals) and call cleanup function
|
|
trap cleanup SIGTERM SIGINT
|
|
|
|
# Create a named pipe
|
|
mkfifo bp
|
|
|
|
# Start background processes
|
|
cat bp | tail -1 >> ./result2.out &
|
|
|
|
# Run main.sh in the same process group
|
|
{bash} ./main.sh "$@" 2>&1 | tee bp &
|
|
|
|
pid=$!
|
|
|
|
# Wait for main.sh to finish and capture its exit status
|
|
wait $pid
|
|
exit_status=$?
|
|
|
|
# Clean up the named pipe and background processes
|
|
rm -f bp
|
|
|
|
|
|
# Exit with the captured status
|
|
exit $exit_status
|
|
"#,
|
|
bash = BIN_BASH.as_str(),
|
|
);
|
|
write_file(job_dir, "wrapper.sh", &script)?;
|
|
|
|
let token = client.get_token().await;
|
|
let mut reserved_variables = get_reserved_variables(job, &token, db).await?;
|
|
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
|
|
|
|
let args = build_args_map(job, client, db).await?.map(Json);
|
|
let job_args = if args.is_some() {
|
|
args.as_ref()
|
|
} else {
|
|
job.args.as_ref()
|
|
};
|
|
|
|
let args_owned = windmill_parser_bash::parse_bash_sig(&content)?
|
|
.args
|
|
.iter()
|
|
.map(|arg| {
|
|
job_args
|
|
.and_then(|x| x.get(&arg.name).map(|x| raw_to_string(x.get())))
|
|
.unwrap_or_else(String::new)
|
|
})
|
|
.collect::<Vec<String>>();
|
|
let args = args_owned.iter().map(|s| &s[..]).collect::<Vec<&str>>();
|
|
let _ = write_file(job_dir, "result.json", "")?;
|
|
let _ = write_file(job_dir, "result.out", "")?;
|
|
let _ = write_file(job_dir, "result2.out", "")?;
|
|
|
|
let child = if !*DISABLE_NSJAIL {
|
|
let _ = write_file(
|
|
job_dir,
|
|
"run.config.proto",
|
|
&NSJAIL_CONFIG_RUN_BASH_CONTENT
|
|
.replace("{JOB_DIR}", job_dir)
|
|
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
|
.replace("{SHARED_MOUNT}", shared_mount),
|
|
)?;
|
|
let mut cmd_args = vec![
|
|
"--config",
|
|
"run.config.proto",
|
|
"--",
|
|
BIN_BASH.as_str(),
|
|
"wrapper.sh",
|
|
];
|
|
cmd_args.extend(args);
|
|
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
|
nsjail_cmd
|
|
.current_dir(job_dir)
|
|
.env_clear()
|
|
.envs(reserved_variables)
|
|
.envs(PROXY_ENVS.clone())
|
|
.env("PATH", PATH_ENV.as_str())
|
|
.env("BASE_INTERNAL_URL", base_internal_url)
|
|
.args(cmd_args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await?
|
|
} else {
|
|
let mut cmd_args = vec!["wrapper.sh"];
|
|
cmd_args.extend(&args);
|
|
let mut bash_cmd = Command::new(BIN_BASH.as_str());
|
|
bash_cmd
|
|
.current_dir(job_dir)
|
|
.env_clear()
|
|
.envs(envs)
|
|
.envs(reserved_variables)
|
|
.env("PATH", PATH_ENV.as_str())
|
|
.env("BASE_INTERNAL_URL", base_internal_url)
|
|
.env("HOME", HOME_ENV.as_str())
|
|
.args(cmd_args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
start_child_process(bash_cmd, BIN_BASH.as_str()).await?
|
|
};
|
|
handle_child(
|
|
&job.id,
|
|
db,
|
|
mem_peak,
|
|
child,
|
|
!*DISABLE_NSJAIL,
|
|
worker_name,
|
|
&job.workspace_id,
|
|
"bash run",
|
|
job.timeout,
|
|
true,
|
|
&mut Some(occupancy_metrics),
|
|
)
|
|
.await?;
|
|
|
|
#[cfg(feature = "dind")]
|
|
if annotation.docker {
|
|
return handle_docker_job(
|
|
job.id,
|
|
&job.workspace_id,
|
|
db,
|
|
job.timeout,
|
|
mem_peak,
|
|
worker_name,
|
|
occupancy_metrics,
|
|
_killpill_rx,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
let result_json_path = format!("{job_dir}/result.json");
|
|
if let Ok(metadata) = tokio::fs::metadata(&result_json_path).await {
|
|
if metadata.len() > 0 {
|
|
return Ok(read_file(&result_json_path).await?);
|
|
}
|
|
}
|
|
|
|
let result_out_path = format!("{job_dir}/result.out");
|
|
if let Ok(metadata) = tokio::fs::metadata(&result_out_path).await {
|
|
if metadata.len() > 0 {
|
|
let result = read_file_content(&result_out_path).await?;
|
|
return Ok(to_raw_value(&json!(result)));
|
|
}
|
|
}
|
|
|
|
let result_out_path2 = format!("{job_dir}/result2.out");
|
|
if tokio::fs::metadata(&result_out_path2).await.is_ok() {
|
|
let result = read_file_content(&result_out_path2)
|
|
.await?
|
|
.trim()
|
|
.to_string();
|
|
|
|
return Ok(to_raw_value(&json!(result)));
|
|
}
|
|
|
|
Ok(to_raw_value(&json!(
|
|
"No result.out, result2.out or result.json found"
|
|
)))
|
|
}
|
|
|
|
#[cfg(feature = "dind")]
|
|
async fn handle_docker_job(
|
|
job_id: Uuid,
|
|
workspace_id: &str,
|
|
db: &DB,
|
|
job_timeout: Option<i32>,
|
|
mem_peak: &mut i32,
|
|
worker_name: &str,
|
|
occupancy_metrics: &mut OccupancyMetrics,
|
|
killpill_rx: &mut tokio::sync::broadcast::Receiver<()>,
|
|
) -> Result<Box<RawValue>, Error> {
|
|
let client = bollard::Docker::connect_with_unix_defaults().map_err(to_anyhow)?;
|
|
|
|
let container_id = job_id.to_string();
|
|
let inspected = client.inspect_container(&container_id, None).await;
|
|
|
|
if inspected.is_err() || inspected.unwrap().state.is_none() {
|
|
return Ok(to_raw_value(&format!(
|
|
"Container not found at {job_id}, you must use --name and not --rm it"
|
|
)));
|
|
}
|
|
|
|
let wait_f = async {
|
|
let wait = client
|
|
.wait_container::<String>(&container_id, None)
|
|
.try_collect::<Vec<_>>()
|
|
.await
|
|
.map_err(|e| {
|
|
tracing::error!("Error waiting for container: {:?}", e);
|
|
anyhow::anyhow!("Error waiting for container")
|
|
})?;
|
|
let waited = wait.first().map(|x| x.status_code);
|
|
Ok(waited)
|
|
};
|
|
|
|
let ncontainer_id = container_id.to_string();
|
|
let w_id = workspace_id.to_string();
|
|
let j_id = job_id.clone();
|
|
let db2 = db.clone();
|
|
let (tx, mut rx) = tokio::sync::broadcast::channel::<()>(1);
|
|
|
|
let mut killpill_rx = killpill_rx.resubscribe();
|
|
let logs = tokio::spawn(async move {
|
|
let client = bollard::Docker::connect_with_unix_defaults().map_err(to_anyhow);
|
|
if let Ok(client) = client {
|
|
let mut log_stream = client.logs(
|
|
&ncontainer_id,
|
|
Some(bollard::container::LogsOptions {
|
|
follow: true,
|
|
stdout: true,
|
|
stderr: true,
|
|
tail: "all",
|
|
..Default::default()
|
|
}),
|
|
);
|
|
loop {
|
|
tokio::select! {
|
|
log = log_stream.next() => {
|
|
match log {
|
|
Some(Ok(log)) => {
|
|
append_logs(&j_id, w_id.clone(), log.to_string(), db2.clone()).await;
|
|
}
|
|
Some(Err(e)) => {
|
|
tracing::error!("Error getting logs: {:?}", e);
|
|
}
|
|
_ => {
|
|
tracing::error!("End of stream");
|
|
return
|
|
}
|
|
};
|
|
},
|
|
_ = killpill_rx.recv() => {
|
|
tracing::error!("killing container after receving killpill");
|
|
if let Err(e) = client
|
|
.stop_container(&ncontainer_id, Some(StopContainerOptions { t: 3 }))
|
|
.await
|
|
{
|
|
tracing::error!("Error stopping container: {:?}", e);
|
|
}
|
|
return
|
|
},
|
|
_ = rx.recv() => {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let mem_client = bollard::Docker::connect_with_unix_defaults().map_err(to_anyhow);
|
|
let ncontainer_id = container_id.clone();
|
|
let result = run_future_with_polling_update_job_poller(
|
|
job_id,
|
|
job_timeout,
|
|
db,
|
|
mem_peak,
|
|
wait_f,
|
|
worker_name,
|
|
workspace_id,
|
|
&mut Some(occupancy_metrics),
|
|
Box::pin(match mem_client {
|
|
Ok(client) => client
|
|
.stats(
|
|
&ncontainer_id,
|
|
Some(StatsOptions { stream: true, one_shot: false }),
|
|
)
|
|
.map(|x| {
|
|
x.map(|x| x.memory_stats.usage.map(|m| m / 1024).unwrap_or_default() as i32)
|
|
.unwrap_or_default()
|
|
})
|
|
.boxed(),
|
|
_ => stream::once(async { 0 }).boxed(),
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
if let Err(e) = result {
|
|
if !logs.is_finished() {
|
|
let _ = tx.send(());
|
|
let _ = logs.await;
|
|
}
|
|
if container_is_alive(&client, &container_id).await {
|
|
kill_container(&client, &container_id, "SIGINT").await;
|
|
if container_is_alive(&client, &container_id).await {
|
|
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
|
|
if let Err(e) = client
|
|
.stop_container(&container_id, Some(StopContainerOptions { t: 3 }))
|
|
.await
|
|
{
|
|
tracing::error!("Error stopping container: {:?}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Err(e);
|
|
}
|
|
|
|
if let Err(e) = client
|
|
.remove_container(
|
|
&container_id,
|
|
Some(RemoveContainerOptions { force: true, ..Default::default() }),
|
|
)
|
|
.await
|
|
{
|
|
tracing::error!("Error removing container: {:?}", e);
|
|
}
|
|
|
|
let result = result.unwrap();
|
|
|
|
return Ok(to_raw_value(&json!(format!(
|
|
"Docker exit status: {}",
|
|
result
|
|
.map(|x| x.to_string())
|
|
.unwrap_or_else(|| "none".to_string())
|
|
))));
|
|
}
|
|
|
|
#[cfg(feature = "dind")]
|
|
async fn kill_container(client: &bollard::Docker, container_id: &str, signal: &str) {
|
|
if let Err(e) = client
|
|
.kill_container(&container_id, Some(KillContainerOptions { signal }))
|
|
.await
|
|
{
|
|
tracing::error!("Error killing container with signal {signal}: {:?}", e);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "dind")]
|
|
async fn container_is_alive(client: &bollard::Docker, container_id: &str) -> bool {
|
|
let inspect = client.inspect_container(container_id, None).await;
|
|
if let Ok(inspect) = inspect {
|
|
let r = inspect
|
|
.state
|
|
.map(|x| x.running.unwrap_or_default())
|
|
.unwrap_or_default();
|
|
tracing::error!("Container {container_id} is alive: {r}");
|
|
r
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn raw_to_string(x: &str) -> String {
|
|
match serde_json::from_str::<serde_json::Value>(x) {
|
|
Ok(serde_json::Value::String(x)) => x,
|
|
Ok(x) => serde_json::to_string(&x).unwrap_or_else(|_| String::new()),
|
|
_ => String::new(),
|
|
}
|
|
}
|
|
#[tracing::instrument(level = "trace", skip_all)]
|
|
pub async fn handle_powershell_job(
|
|
mem_peak: &mut i32,
|
|
job: &QueuedJob,
|
|
db: &sqlx::Pool<sqlx::Postgres>,
|
|
client: &AuthedClientBackgroundTask,
|
|
content: &str,
|
|
job_dir: &str,
|
|
shared_mount: &str,
|
|
base_internal_url: &str,
|
|
worker_name: &str,
|
|
envs: HashMap<String, String>,
|
|
occupancy_metrics: &mut OccupancyMetrics,
|
|
) -> Result<Box<RawValue>, Error> {
|
|
let pwsh_args = {
|
|
let args = build_args_map(job, client, db).await?.map(Json);
|
|
let job_args = if args.is_some() {
|
|
args.as_ref()
|
|
} else {
|
|
job.args.as_ref()
|
|
};
|
|
|
|
let args_owned = windmill_parser_bash::parse_powershell_sig(&content)?
|
|
.args
|
|
.iter()
|
|
.map(|arg| {
|
|
(
|
|
arg.name.clone(),
|
|
job_args
|
|
.and_then(|x| x.get(&arg.name).map(|x| raw_to_string(x.get())))
|
|
.unwrap_or_else(String::new),
|
|
)
|
|
})
|
|
.collect::<Vec<(String, String)>>();
|
|
args_owned
|
|
.iter()
|
|
.map(|(n, v)| vec![format!("--{n}"), format!("{v}")])
|
|
.flatten()
|
|
.collect::<Vec<_>>()
|
|
};
|
|
|
|
#[cfg(windows)]
|
|
let split_char = '\\';
|
|
|
|
#[cfg(unix)]
|
|
let split_char = '/';
|
|
|
|
let installed_modules = fs::read_dir(POWERSHELL_CACHE_DIR)?
|
|
.filter_map(|x| {
|
|
x.ok().map(|x| {
|
|
x.path()
|
|
.display()
|
|
.to_string()
|
|
.split(split_char)
|
|
.last()
|
|
.unwrap_or_default()
|
|
.to_lowercase()
|
|
})
|
|
})
|
|
.collect::<Vec<String>>();
|
|
|
|
let mut install_string: String = String::new();
|
|
let mut logs1 = String::new();
|
|
for line in content.lines() {
|
|
for cap in RE_POWERSHELL_IMPORTS.captures_iter(line) {
|
|
let module = cap.get(1).unwrap().as_str();
|
|
if !installed_modules.contains(&module.to_lowercase()) {
|
|
logs1.push_str(&format!("\n{} not found in cache", module.to_string()));
|
|
// instead of using Install-Module, we use Save-Module so that we can specify the installation path
|
|
install_string.push_str(&format!(
|
|
"Save-Module -Path {} -Force {};",
|
|
POWERSHELL_CACHE_DIR, module
|
|
));
|
|
} else {
|
|
logs1.push_str(&format!("\n{} found in cache", module.to_string()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if !install_string.is_empty() {
|
|
logs1.push_str("\n\nInstalling modules...");
|
|
append_logs(&job.id, &job.workspace_id, logs1, db).await;
|
|
let child = Command::new(POWERSHELL_PATH.as_str())
|
|
.args(&["-Command", &install_string])
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()?;
|
|
|
|
handle_child(
|
|
&job.id,
|
|
db,
|
|
mem_peak,
|
|
child,
|
|
false,
|
|
worker_name,
|
|
&job.workspace_id,
|
|
"powershell install",
|
|
job.timeout,
|
|
false,
|
|
&mut Some(occupancy_metrics),
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
let mut logs2 = "".to_string();
|
|
logs2.push_str("\n\n--- POWERSHELL CODE EXECUTION ---\n");
|
|
append_logs(&job.id, &job.workspace_id, logs2, db).await;
|
|
|
|
// make sure default (only allhostsallusers) modules are loaded, disable autoload (cache can be large to explore especially on cloud) and add /tmp/windmill/cache to PSModulePath
|
|
#[cfg(unix)]
|
|
let profile = format!(
|
|
"$PSModuleAutoloadingPreference = 'None'
|
|
$PSModulePathBackup = $env:PSModulePath
|
|
$env:PSModulePath = \"$PSHome/Modules\"
|
|
Get-Module -ListAvailable | Import-Module
|
|
$env:PSModulePath = \"{}:$PSModulePathBackup\"",
|
|
POWERSHELL_CACHE_DIR
|
|
);
|
|
|
|
#[cfg(windows)]
|
|
let profile = format!(
|
|
"$PSModuleAutoloadingPreference = 'None'
|
|
$PSModulePathBackup = $env:PSModulePath
|
|
$env:PSModulePath = \"C:\\Program Files\\PowerShell\\7\\Modules\"
|
|
Get-Module -ListAvailable | Import-Module
|
|
$env:PSModulePath = \"{};$PSModulePathBackup\"",
|
|
POWERSHELL_CACHE_DIR
|
|
);
|
|
|
|
// NOTE: powershell error handling / termination is quite tricky compared to bash
|
|
// here we're trying to catch terminating errors and propagate the exit code
|
|
// to the caller such that the job will be marked as failed. It's up to the user
|
|
// to catch specific errors in their script not caught by the below as there is no
|
|
// generic set -eu as in bash
|
|
let strict_termination_start = "$ErrorActionPreference = 'Stop'\n\
|
|
Set-StrictMode -Version Latest\n\
|
|
try {\n";
|
|
|
|
let strict_termination_end = "\n\
|
|
} catch {\n\
|
|
Write-Output \"An error occurred:\n\"\
|
|
Write-Output $_
|
|
exit 1\n\
|
|
}\n";
|
|
|
|
// make sure param() is first
|
|
let param_match = windmill_parser_bash::RE_POWERSHELL_PARAM.find(&content);
|
|
let content: String = if let Some(param_match) = param_match {
|
|
let param_match = param_match.as_str();
|
|
format!(
|
|
"{}\n{}\n{}\n{}\n{}",
|
|
param_match,
|
|
profile,
|
|
strict_termination_start,
|
|
content.replace(param_match, ""),
|
|
strict_termination_end
|
|
)
|
|
} else {
|
|
format!("{}\n{}", profile, content)
|
|
};
|
|
|
|
write_file(job_dir, "main.ps1", content.as_str())?;
|
|
|
|
#[cfg(unix)]
|
|
write_file(
|
|
job_dir,
|
|
"wrapper.sh",
|
|
&format!("set -o pipefail\nset -e\nmkfifo bp\ncat bp | tail -1 > ./result2.out &\n{} -F ./main.ps1 \"$@\" 2>&1 | tee bp\nwait $!", POWERSHELL_PATH.as_str()),
|
|
)?;
|
|
|
|
#[cfg(windows)]
|
|
write_file(
|
|
job_dir,
|
|
"wrapper.ps1",
|
|
&format!(
|
|
"param([string[]]$args)\n\
|
|
$ErrorActionPreference = 'Stop'\n\
|
|
$pipe = New-TemporaryFile\n\
|
|
& \"{}\" -File ./main.ps1 @args 2>&1 | Tee-Object -FilePath $pipe\n\
|
|
Get-Content -Path $pipe | Select-Object -Last 1 | Set-Content -Path './result2.out'\n\
|
|
Remove-Item $pipe\n\
|
|
exit $LASTEXITCODE\n",
|
|
POWERSHELL_PATH.as_str()
|
|
),
|
|
)?;
|
|
|
|
let token = client.get_token().await;
|
|
let mut reserved_variables = get_reserved_variables(job, &token, db).await?;
|
|
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
|
|
|
|
let _ = write_file(job_dir, "result.json", "")?;
|
|
let _ = write_file(job_dir, "result.out", "")?;
|
|
let _ = write_file(job_dir, "result2.out", "")?;
|
|
|
|
let child = if !*DISABLE_NSJAIL {
|
|
let _ = write_file(
|
|
job_dir,
|
|
"run.config.proto",
|
|
&NSJAIL_CONFIG_RUN_POWERSHELL_CONTENT
|
|
.replace("{JOB_DIR}", job_dir)
|
|
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
|
.replace("{SHARED_MOUNT}", shared_mount)
|
|
.replace("{CACHE_DIR}", POWERSHELL_CACHE_DIR),
|
|
)?;
|
|
let mut cmd_args = vec![
|
|
"--config",
|
|
"run.config.proto",
|
|
"--",
|
|
BIN_BASH.as_str(),
|
|
"wrapper.sh",
|
|
];
|
|
cmd_args.extend(pwsh_args.iter().map(|x| x.as_str()));
|
|
Command::new(NSJAIL_PATH.as_str())
|
|
.current_dir(job_dir)
|
|
.env_clear()
|
|
.envs(PROXY_ENVS.clone())
|
|
.envs(reserved_variables)
|
|
.env("TZ", TZ_ENV.as_str())
|
|
.env("PATH", PATH_ENV.as_str())
|
|
.env("BASE_INTERNAL_URL", base_internal_url)
|
|
.args(cmd_args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()?
|
|
} else {
|
|
let mut cmd;
|
|
let mut cmd_args;
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
cmd_args = vec!["wrapper.sh"];
|
|
cmd_args.extend(pwsh_args.iter().map(|x| x.as_str()));
|
|
cmd = Command::new(BIN_BASH.as_str());
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
{
|
|
cmd_args = vec![r".\wrapper.ps1".to_string()];
|
|
cmd_args.extend(pwsh_args.iter().map(|x| x.replace("--", "-")));
|
|
cmd = Command::new(POWERSHELL_PATH.as_str());
|
|
}
|
|
|
|
cmd.current_dir(job_dir)
|
|
.env_clear()
|
|
.envs(envs)
|
|
.envs(reserved_variables)
|
|
.env("TZ", TZ_ENV.as_str())
|
|
.env("PATH", PATH_ENV.as_str())
|
|
.env("BASE_INTERNAL_URL", base_internal_url)
|
|
.env("HOME", HOME_ENV.as_str())
|
|
.args(&cmd_args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
|
|
#[cfg(windows)]
|
|
{
|
|
cmd.env("SystemRoot", SYSTEM_ROOT.as_str())
|
|
.env(
|
|
"LOCALAPPDATA",
|
|
std::env::var("LOCALAPPDATA")
|
|
.unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())),
|
|
)
|
|
.env(
|
|
"ProgramData",
|
|
std::env::var("ProgramData")
|
|
.unwrap_or_else(|_| String::from("C:\\ProgramData")),
|
|
)
|
|
.env(
|
|
"ProgramFiles",
|
|
std::env::var("ProgramFiles")
|
|
.unwrap_or_else(|_| String::from("C:\\Program Files")),
|
|
)
|
|
.env(
|
|
"ProgramFiles(x86)",
|
|
std::env::var("ProgramFiles(x86)")
|
|
.unwrap_or_else(|_| String::from("C:\\Program Files (x86)")),
|
|
)
|
|
.env(
|
|
"ProgramW6432",
|
|
std::env::var("ProgramW6432")
|
|
.unwrap_or_else(|_| String::from("C:\\Program Files")),
|
|
)
|
|
.env(
|
|
"TMP",
|
|
std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")),
|
|
)
|
|
.env(
|
|
"PATHEXT",
|
|
std::env::var("PATHEXT").unwrap_or_else(|_| {
|
|
String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL")
|
|
}),
|
|
)
|
|
.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
|
}
|
|
|
|
cmd.spawn()?
|
|
};
|
|
|
|
handle_child(
|
|
&job.id,
|
|
db,
|
|
mem_peak,
|
|
child,
|
|
!*DISABLE_NSJAIL,
|
|
worker_name,
|
|
&job.workspace_id,
|
|
"powershell run",
|
|
job.timeout,
|
|
false,
|
|
&mut Some(occupancy_metrics),
|
|
)
|
|
.await?;
|
|
|
|
let result_json_path = format!("{job_dir}/result.json");
|
|
if let Ok(metadata) = tokio::fs::metadata(&result_json_path).await {
|
|
if metadata.len() > 0 {
|
|
return Ok(read_file(&result_json_path).await?);
|
|
}
|
|
}
|
|
|
|
let result_out_path = format!("{job_dir}/result.out");
|
|
if let Ok(metadata) = tokio::fs::metadata(&result_out_path).await {
|
|
if metadata.len() > 0 {
|
|
let result = read_file_content(&result_out_path).await?;
|
|
return Ok(to_raw_value(&json!(result)));
|
|
}
|
|
}
|
|
|
|
let result_out_path2 = format!("{job_dir}/result2.out");
|
|
if tokio::fs::metadata(&result_out_path2).await.is_ok() {
|
|
let result = read_file_content(&result_out_path2)
|
|
.await?
|
|
.trim()
|
|
.to_string();
|
|
return Ok(to_raw_value(&json!(result)));
|
|
}
|
|
|
|
Ok(to_raw_value(&json!(
|
|
"No result.out, result2.out or result.json found"
|
|
)))
|
|
}
|