mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 08:05:44 +00:00
+1








82620098d2
* feat: add stream output feature to SSE job updates Adds stream_output field to JobUpdate struct that extracts log lines starting with '[wm_stream]:' from job logs. Regular logs now exclude stream lines, which are captured separately for specialized handling. - Added stream_output: Option<String> field to JobUpdate struct - Created extract_stream_output_from_logs() function to filter stream lines - Modified get_job_update_data() to use stream extraction logic - SSE clients now receive both new_logs and stream_output in job updates Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * feat: rename stream_output to stream and handle newlines as \n - Renamed `stream_output` field to `stream` in JobUpdate struct - Updated extract_stream_output_from_logs to extract_stream_from_logs - Changed stream output to join with literal \n instead of actual newlines - Stream lines are properly excluded from regular new_logs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * decision tree nits * push ee ref * push ee ref * fix: fix id renaming in apps * remove duplicate caching (#6285) * feat: migrate audit log ids to bigints (blocking migration for EE) * fix(mcp): add proper check for mcp routes (#6282) * add proper check for mcp routes * cleaner * apply to flow * fix add checks scopes --------- Co-authored-by: dieriba <dieriba.pro@gmail.com> * chore(main): release 1.514.0 (#6283) * chore(main): release 1.514.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> * fix: pin tokio to 1.46.1 and aws-sdks-ts * pin rustls to 0.23.29 + pin aws-sdk * chore(main): release 1.514.1 (#6288) * chore(main): release 1.514.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> * fix: improve docker logs collection in docker mode * support $res: string in form inputs of arrays * fix import nit * fix: fix DynSelect * nits * fix: resource-type-ts-parser (#6289) * fix: resource types as arg in typescript handle imported defined types * Update nix flake (#6291) * merge * Small UI fixes (#6294) * fix step history not refreshing with staticInputs * fix array of obj not showing up in json editor in test this step * datatable scales correctly in DisplayResult and scrolling is much more usable * avoid next button disapearing and changing layout / hurting ux * nits * fix bug when renaming module A to B then module C to A, C takes the schema of A * fix bug with comments in sql repl * fix aggrid theme randomly not loading * bindable script * better delete button in db manager * property select doesnt exist * fix all warnings * delete $flowStateStore[id] on delete * feat(cli): generate cursor rules on init (#6270) * create cursor rules on init * change gen * add missing resource-type command * add resource type command in guidance * add schema option * revert * nit * nit * add flow guidance * nit * chore(main): release 1.515.0 (#6292) * chore(main): release 1.515.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> * fix: improved logs for script * nits logs * chore(main): release 1.515.1 (#6295) * chore(main): release 1.515.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> * merge * even more indexer tracings * add more tracing logs * feat: prevent too large results (>500Mb) from OOMing database * nit naming * feat: add CA certificate update at startup via environment variable (#6280) * feat: add CA certificate update at startup via environment variable Add support for running 'update-ca-certificates' at binary startup when RUN_UPDATE_CA_CERTIFICATE_AT_START environment variable is set to "true". - Check for RUN_UPDATE_CA_CERTIFICATE_AT_START env var on startup - Execute update-ca-certificates command if env var is set to "true" - Log success/failure appropriately with tracing - Continue startup even if CA certificate update fails - Non-blocking implementation with proper error handling Fixes #6279 Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * refactor: extract CA certificate update logic into separate function Extract the CA certificate update logic from windmill_main() into a dedicated update_ca_certificates_if_requested() function for better code organization and maintainability. Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * improvements --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com> Co-authored-by: Alexander Petric <alex@windmill.dev> * fix: indexer collection of job logs before indexing (#6300) * Add flume as dependecy for indexer * Update ee-repo-ref * Remove flags from cargo.toml * Update ee-repo-ref * Update ee-repo-ref * fix rust sdk build error (#6305) Signed-off-by: pyranota <pyra@duck.com> * fix broken audit logs filter (#6304) * rename to from to * goto fix * default to false if field not present operator settings (#6301) * git sync UI improvements (#6303) * ui improvements round 1 * modal cleanup * init * UI refactor * UI cleanup + refactor * legacy cleanup * success model -> github actions, non-ee warnings * sqlx * npm check * ee warning everywhere * last comments * formatting * no hardcoded theme * claude review improvemenets * fix: no process relative imports for scripts with codebase * fix: sqs oidc authentication disconnect #6307 * handle metadata for new scripts happen after commit * handle_deployment_metadata in a task * nits * chore: add windmill-utils-internal package (#6299) * add utils package * naming * cleaning * add docs * remove log * use autogenerated types * remove old * fix * cleaning * add docs * chore(main): release 1.516.0 (#6298) * chore(main): release 1.516.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> * merge * indexer improvements * upgrade tantivy to 0.24.2 * use tantivy fork * nit warnings * fix oss build * improve indexer * chore: use windmill-utils-internal for cli (#6297) * add utils package * naming * cleaning * simplify assignPath * rename old files * same for locks * create on confirm * default true * use replaceinlinescripts from utils * use extractscriptfromflows * make it compile * cleaning * use argsigtojson * fix * fix missing await * cleaner * cleaning * cleaning * use in frontend * add docs * testing * remove log * use autogenerated types * remove old * fix * cleaning * adapt usage * draft * better build script * fix build * revert to default creation * add docs * remove and rename * make everything work * add await * only if not installed * add vs code setting * add to publish action * fix bc * safer use of sep * fix * do not rename on push * no publish on release * use published package on frontend * nit * Add dependencies to run sqlx prepare to nix flake (#6309) * feat(cli): wmill-lock.yaml v2 for easier git merge diffs * merge * merge * all * all * rm warnings * fix styling on aichatinput (#6312) * fix: use with_capacity back presusre for tantivy directory multipart writes (#6313) * use with capacity for tantivy directory multi part uploads * Update ee repo ref * Update ee-repo-ref * Update ee-repo-ref * chore(main): release 1.517.0 (#6310) * chore(main): release 1.517.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> * fix typo on cli build (#6314) * cleanup * feat(utils): add flow.yaml validation function (#6316) * add validateflow function * cleaner code * preprocess json * cleaning * create specific package * cleaning * add tests * fix: cleanup concurrency_counter automatically + remove orphans keys automatically * fix: add disabled support to resource picker in schema forms * fix: add wm_labels to tracing spans * all * merge * all * fix: delete empty git connection (#6318) * fix checks * bun handling * all * all? * all * all * update * all * update * check * fix history * all * all * all * Remove leftover debug tracing statements - Remove commented debug trace in jobs.rs for stream output - Remove commented debug trace in result_stream.rs for stream processing Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * fix test * all * handle iter * fix --------- Signed-off-by: pyranota <pyra@duck.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: centdix <40307056+centdix@users.noreply.github.com> Co-authored-by: dieriba <dieriba.pro@gmail.com> Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> Co-authored-by: wendrul <53628737+wendrul@users.noreply.github.com> Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com> Co-authored-by: Alexander Petric <alex@windmill.dev> Co-authored-by: pyranota <92104930+pyranota@users.noreply.github.com>
349 lines
10 KiB
Rust
349 lines
10 KiB
Rust
use convert_case::{Case, Casing};
|
|
use itertools::Itertools;
|
|
use regex::Regex;
|
|
use serde_json::value::RawValue;
|
|
use std::{collections::HashMap, process::Stdio};
|
|
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
|
use uuid::Uuid;
|
|
use windmill_common::{
|
|
error::{self, to_anyhow, Result},
|
|
worker::{write_file, Connection},
|
|
};
|
|
use windmill_queue::MiniPulledJob;
|
|
|
|
use windmill_parser::Typ;
|
|
use windmill_queue::{append_logs, CanceledBy};
|
|
|
|
use crate::{
|
|
common::{
|
|
check_executor_binary_exists, create_args_and_out_file, get_reserved_variables,
|
|
read_result, start_child_process, OccupancyMetrics,
|
|
},
|
|
handle_child::handle_child,
|
|
COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PHP_PATH,
|
|
};
|
|
use windmill_common::client::AuthedClient;
|
|
|
|
const NSJAIL_CONFIG_RUN_PHP_CONTENT: &str = include_str!("../nsjail/run.php.config.proto");
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref RE: Regex = Regex::new(r"^//\s?(\S+)\s*$").unwrap();
|
|
}
|
|
|
|
const COMPOSER_LOCK_SPLIT: &str = "\nLOCK\n";
|
|
|
|
pub fn parse_php_imports(code: &str) -> anyhow::Result<Option<String>> {
|
|
let find_requirements = code
|
|
.lines()
|
|
.find_position(|x| x.starts_with("//require:") || x.starts_with("// require:"));
|
|
|
|
if let Some((pos, _)) = find_requirements {
|
|
let requirements = code
|
|
.lines()
|
|
.skip(pos + 1)
|
|
.map_while(|x| {
|
|
RE.captures(x).map(|x| {
|
|
match x.get(1).unwrap().as_str().split("@").collect_vec()[..] {
|
|
[path, version] => (path.to_string(), version.to_string()),
|
|
[path] | [path, ..] => (path.to_string(), "*".to_string()),
|
|
[] => unreachable!(),
|
|
}
|
|
})
|
|
})
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
let composer_json =
|
|
serde_json::to_string_pretty(&HashMap::from([("require", requirements)]))
|
|
.map_err(to_anyhow)?;
|
|
|
|
Ok(Some(composer_json))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub async fn composer_install(
|
|
mem_peak: &mut i32,
|
|
canceled_by: &mut Option<CanceledBy>,
|
|
job_id: &Uuid,
|
|
w_id: &str,
|
|
conn: &Connection,
|
|
job_dir: &str,
|
|
worker_name: &str,
|
|
requirements: String,
|
|
lock: Option<String>,
|
|
occupancy_metrics: &mut OccupancyMetrics,
|
|
) -> Result<String> {
|
|
check_executor_binary_exists("php", PHP_PATH.as_str(), "php")?;
|
|
|
|
write_file(job_dir, "composer.json", &requirements)?;
|
|
|
|
if let Some(lock) = lock.as_ref() {
|
|
write_file(job_dir, "composer.lock", lock)?;
|
|
}
|
|
|
|
let mut child_cmd = Command::new(&*COMPOSER_PATH);
|
|
let args = vec!["install", "--no-dev", "--no-progress"];
|
|
child_cmd
|
|
.current_dir(job_dir)
|
|
.env("COMPOSER_HOME", &*COMPOSER_CACHE_DIR)
|
|
.args(args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
let child_process = start_child_process(child_cmd, &*COMPOSER_PATH).await?;
|
|
|
|
handle_child(
|
|
job_id,
|
|
conn,
|
|
mem_peak,
|
|
canceled_by,
|
|
child_process,
|
|
false,
|
|
worker_name,
|
|
w_id,
|
|
"composer install",
|
|
None,
|
|
false,
|
|
&mut Some(occupancy_metrics),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
match lock {
|
|
Some(lock) => Ok(format!("{requirements}{COMPOSER_LOCK_SPLIT}{lock}")),
|
|
None => {
|
|
let mut lock_content = "".to_string();
|
|
let mut lock_file = File::open(format!("{job_dir}/composer.lock")).await?;
|
|
lock_file.read_to_string(&mut lock_content).await?;
|
|
Ok(format!("{requirements}{COMPOSER_LOCK_SPLIT}{lock_content}"))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_resource_class(rt_name: &str, arg_name: &str) -> String {
|
|
let rt_name = rt_name.to_case(Case::Pascal);
|
|
format!(
|
|
"#[AllowDynamicProperties]
|
|
class {rt_name} {{
|
|
public function __construct($data) {{
|
|
foreach ($data AS $key => $value) $this->{{$key}} = $value;
|
|
}}
|
|
}}
|
|
$args->{arg_name} = new {rt_name}($args->{arg_name});"
|
|
)
|
|
}
|
|
|
|
#[tracing::instrument(level = "trace", skip_all)]
|
|
pub async fn handle_php_job(
|
|
requirements_o: Option<&String>,
|
|
mem_peak: &mut i32,
|
|
canceled_by: &mut Option<CanceledBy>,
|
|
job: &MiniPulledJob,
|
|
conn: &Connection,
|
|
client: &AuthedClient,
|
|
parent_runnable_path: Option<String>,
|
|
job_dir: &str,
|
|
inner_content: &String,
|
|
base_internal_url: &str,
|
|
worker_name: &str,
|
|
envs: HashMap<String, String>,
|
|
shared_mount: &str,
|
|
occupancy_metrics: &mut OccupancyMetrics,
|
|
) -> error::Result<Box<RawValue>> {
|
|
check_executor_binary_exists("php", PHP_PATH.as_str(), "php")?;
|
|
|
|
let (composer_json, composer_lock) = match requirements_o {
|
|
Some(reqs_and_lock) if !reqs_and_lock.is_empty() => {
|
|
let splitted = reqs_and_lock.split(COMPOSER_LOCK_SPLIT).collect_vec();
|
|
if splitted.len() != 2 {
|
|
return Err(error::Error::ExecutionErr(
|
|
format!("Invalid requirements, expected to find LOCK split pattern in reqs. Found: |{reqs_and_lock}|")
|
|
));
|
|
}
|
|
(Some(splitted[0].to_string()), Some(splitted[1].to_string()))
|
|
}
|
|
_ => (parse_php_imports(inner_content)?, None),
|
|
};
|
|
|
|
let autoload_line = if let Some(composer_json) = composer_json {
|
|
let logs1 = "\n\n--- COMPOSER INSTALL ---\n".to_string();
|
|
append_logs(&job.id, &job.workspace_id, logs1, conn).await;
|
|
|
|
composer_install(
|
|
mem_peak,
|
|
canceled_by,
|
|
&job.id,
|
|
&job.workspace_id,
|
|
conn,
|
|
job_dir,
|
|
worker_name,
|
|
composer_json,
|
|
composer_lock,
|
|
occupancy_metrics,
|
|
)
|
|
.await?;
|
|
"require './vendor/autoload.php';"
|
|
} else {
|
|
""
|
|
};
|
|
|
|
let init_logs = "\n\n--- PHP CODE EXECUTION ---\n".to_string();
|
|
|
|
append_logs(&job.id, job.workspace_id.to_string(), init_logs, conn).await;
|
|
|
|
let _ = write_file(job_dir, "main.php", inner_content)?;
|
|
|
|
let main_override = job.script_entrypoint_override.as_deref();
|
|
|
|
let write_wrapper_f = async {
|
|
let args = windmill_parser_php::parse_php_signature(
|
|
inner_content,
|
|
main_override.map(ToString::to_string),
|
|
)?
|
|
.args;
|
|
|
|
let args_to_include = args
|
|
.iter()
|
|
.filter(|x| {
|
|
!x.has_default || job.args.as_ref().is_some_and(|a| a.contains_key(&x.name))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let func_args_str = args_to_include
|
|
.iter()
|
|
.map(|x| format!("$args->{}", x.name))
|
|
.collect::<Vec<String>>()
|
|
.join(",");
|
|
|
|
let resource_classes = args_to_include
|
|
.iter()
|
|
.filter_map(|x| match &x.typ {
|
|
Typ::Resource(name) => Some((name, &x.name)),
|
|
_ => None,
|
|
})
|
|
.unique()
|
|
.map(|(rt_name, arg_name)| generate_resource_class(rt_name, arg_name))
|
|
.collect::<Vec<_>>()
|
|
.join("\n\n");
|
|
|
|
let main_name = main_override.unwrap_or("main");
|
|
|
|
let wrapper_content: String = format!(
|
|
r#"
|
|
<?php
|
|
{autoload_line}
|
|
require './main.php';
|
|
|
|
$args_str = file_get_contents('./args.json');
|
|
$args = json_decode($args_str);
|
|
|
|
{resource_classes}
|
|
|
|
try {{
|
|
$res = {main_name}({func_args_str});
|
|
file_put_contents("result.json", json_encode($res));
|
|
}} catch (Exception $e) {{
|
|
$err = [
|
|
"message" => $e->getMessage(),
|
|
"name" => get_class($e),
|
|
"stack" => $e->getTraceAsString()
|
|
];
|
|
$step_id = getenv('WM_FLOW_STEP_ID');
|
|
if ($step_id) {{
|
|
$err["step_id"] = $step_id;
|
|
}}
|
|
file_put_contents("result.json", json_encode($err));
|
|
exit(1);
|
|
}}
|
|
"#,
|
|
);
|
|
write_file(job_dir, "wrapper.php", &wrapper_content)?;
|
|
Ok(()) as error::Result<()>
|
|
};
|
|
|
|
let reserved_variables_args_out_f = async {
|
|
let args_and_out_f = async {
|
|
create_args_and_out_file(&client, job, job_dir, conn).await?;
|
|
Ok(()) as Result<()>
|
|
};
|
|
let reserved_variables_f = async {
|
|
let vars =
|
|
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone())
|
|
.await?;
|
|
Ok(vars) as Result<HashMap<String, String>>
|
|
};
|
|
let (_, reserved_variables) = tokio::try_join!(args_and_out_f, reserved_variables_f)?;
|
|
Ok(reserved_variables) as error::Result<HashMap<String, String>>
|
|
};
|
|
|
|
let (reserved_variables, _) = tokio::try_join!(reserved_variables_args_out_f, write_wrapper_f)?;
|
|
|
|
let child = if !*DISABLE_NSJAIL {
|
|
let _ = write_file(
|
|
job_dir,
|
|
"run.config.proto",
|
|
&NSJAIL_CONFIG_RUN_PHP_CONTENT
|
|
.replace("{JOB_DIR}", job_dir)
|
|
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
|
.replace("{SHARED_MOUNT}", shared_mount),
|
|
)?;
|
|
|
|
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
|
let args = vec![
|
|
"--config",
|
|
"run.config.proto",
|
|
"--",
|
|
&PHP_PATH,
|
|
"/tmp/wrapper.php",
|
|
];
|
|
nsjail_cmd
|
|
.current_dir(job_dir)
|
|
.env_clear()
|
|
.envs(envs)
|
|
.envs(reserved_variables)
|
|
.env("COMPOSER_HOME", &*COMPOSER_CACHE_DIR)
|
|
.env("BASE_INTERNAL_URL", base_internal_url)
|
|
.args(args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await?
|
|
} else {
|
|
let cmd = {
|
|
let script_path = format!("{job_dir}/wrapper.php");
|
|
|
|
let mut php_cmd = Command::new(&*PHP_PATH);
|
|
let args = vec![&script_path];
|
|
php_cmd
|
|
.current_dir(job_dir)
|
|
.env_clear()
|
|
.envs(envs)
|
|
.envs(reserved_variables)
|
|
.env("COMPOSER_HOME", &*COMPOSER_CACHE_DIR)
|
|
.env("BASE_INTERNAL_URL", base_internal_url)
|
|
.args(args)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
php_cmd
|
|
};
|
|
start_child_process(cmd, &*PHP_PATH).await?
|
|
};
|
|
|
|
handle_child(
|
|
&job.id,
|
|
conn,
|
|
mem_peak,
|
|
canceled_by,
|
|
child,
|
|
!*DISABLE_NSJAIL,
|
|
worker_name,
|
|
&job.workspace_id,
|
|
"php run",
|
|
job.timeout,
|
|
false,
|
|
&mut Some(occupancy_metrics),
|
|
None,
|
|
)
|
|
.await?;
|
|
read_result(job_dir, None).await
|
|
}
|