diff --git a/backend/.sqlx/query-27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8.json b/backend/.sqlx/query-27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8.json new file mode 100644 index 0000000000..6d127f4c83 --- /dev/null +++ b/backend/.sqlx/query-27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT draft_only FROM app WHERE path = $1 AND workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "draft_only", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8" +} diff --git a/backend/.sqlx/query-28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60.json b/backend/.sqlx/query-28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60.json new file mode 100644 index 0000000000..a5c5e9427e --- /dev/null +++ b/backend/.sqlx/query-28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT draft_only FROM flow WHERE path = $1 AND workspace_id = $2 AND archived = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "draft_only", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60" +} diff --git a/backend/.sqlx/query-71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345.json b/backend/.sqlx/query-71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345.json new file mode 100644 index 0000000000..da3b4cc1b9 --- /dev/null +++ b/backend/.sqlx/query-71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT draft_only FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "draft_only", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345" +} diff --git a/backend/parsers/windmill-parser-bash/src/lib.rs b/backend/parsers/windmill-parser-bash/src/lib.rs index 69d96400dc..f179243d68 100644 --- a/backend/parsers/windmill-parser-bash/src/lib.rs +++ b/backend/parsers/windmill-parser-bash/src/lib.rs @@ -10,7 +10,7 @@ use regex_lite::Regex; use serde_json::json; use std::{collections::HashMap, str::FromStr}; -use windmill_parser::{Arg, MainArgSignature, Typ}; +use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ}; pub fn parse_bash_sig(code: &str) -> anyhow::Result { let parsed = parse_bash_file(&code)?; @@ -48,7 +48,7 @@ lazy_static::lazy_static! { static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?$"#).unwrap(); pub static ref RE_POWERSHELL_PARAM: Regex = Regex::new(r#"(?m)param[\t ]*\(([^)]*)\)"#).unwrap(); - static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[(\w+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?"#).unwrap(); + static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[([\w\[\]]+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?"#).unwrap(); } fn parse_bash_file(code: &str) -> anyhow::Result>> { @@ -124,6 +124,18 @@ fn split_pwsh_args(code: &str) -> Vec<&str> { splits } +fn parse_powershell_single_typ(typ: &str) -> Typ { + match typ.to_lowercase().as_str() { + "string" => Typ::Str(None), + "int" | "long" => Typ::Int, + "decimal" | "double" | "single" => Typ::Float, + "datetime" => Typ::Datetime, + "bool" => Typ::Bool, + "pscustomobject" => Typ::Object(ObjectType::new(None, None)), + _ => Typ::Str(None), + } +} + fn parse_powershell_file(code: &str) -> anyhow::Result>> { let param_wrapper = RE_POWERSHELL_PARAM.captures(code); let mut args = vec![]; @@ -136,12 +148,12 @@ fn parse_powershell_file(code: &str) -> anyhow::Result>> { let name = cap.get(2).unwrap().as_str().to_string(); let mut parsed_typ = if let Some(typ) = typ { - match typ.as_str() { - "string" => Some(Typ::Str(None)), - "int" | "long" => Some(Typ::Int), - "decimal" | "double" | "single" => Some(Typ::Float), - "datetime" | "DateTime" => Some(Typ::Datetime), - _ => None, + if typ.as_str().ends_with("[]") { + Some(Typ::List(Box::new(parse_powershell_single_typ( + typ.as_str().strip_suffix("[]").unwrap(), + )))) + } else { + Some(parse_powershell_single_typ(typ.as_str())) } } else { None @@ -254,7 +266,7 @@ non_required="${5:-}" #[test] fn test_parse_powershell_sig() -> anyhow::Result<()> { - let code = r#"param($Msg, [string]$Msg2, $Dflt = "default value, with comma", [int]$Nb = 3 , $Nb2 = 5.0, $Nb3 = 5, $Wahoo = $env:WAHOO)"#; + let code = r#"param($Msg, [string]$Msg2, $Dflt = "default value, with comma", [int]$Nb = 3 , $Nb2 = 5.0, $Nb3 = 5, $Wahoo = $env:WAHOO, [PSCustomObject]$Obj, [string[]]$Arr)"#; assert_eq!( parse_powershell_sig(code)?, MainArgSignature { @@ -316,6 +328,22 @@ non_required="${5:-}" default: None, has_default: false, oidx: None + }, + Arg { + otyp: None, + name: "Obj".to_string(), + typ: Typ::Object(ObjectType::new(None, None)), + default: None, + has_default: false, + oidx: None + }, + Arg { + otyp: None, + name: "Arr".to_string(), + typ: Typ::List(Box::new(Typ::Str(None))), + default: None, + has_default: false, + oidx: None } ], no_main_func: None, diff --git a/backend/windmill-worker/nsjail/run.powershell.config.proto b/backend/windmill-worker/nsjail/run.powershell.config.proto index f7580b7aa7..87b6abda21 100644 --- a/backend/windmill-worker/nsjail/run.powershell.config.proto +++ b/backend/windmill-worker/nsjail/run.powershell.config.proto @@ -78,8 +78,8 @@ mount { } mount { - src: "{JOB_DIR}/wrapper.sh" - dst: "/tmp/wrapper.sh" + src: "{JOB_DIR}/wrapper.ps1" + dst: "/tmp/wrapper.ps1" is_bind: true mandatory: false } diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 0e86a65cc2..e37e874ab8 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -58,6 +58,14 @@ lazy_static::lazy_static! { pub static ref ANSI_ESCAPE_RE: Regex = Regex::new(r"\x1b\[[0-9;]*m").unwrap(); } +fn raw_to_string(x: &str) -> String { + match serde_json::from_str::(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_bash_job( mem_peak: &mut i32, @@ -512,11 +520,37 @@ async fn container_is_alive(client: &bollard::Docker, container_id: &str) -> boo } } -fn raw_to_string(x: &str) -> String { +fn val_to_pwsh_param(v: serde_json::Value) -> String { + match v { + serde_json::Value::Array(x) => format!( + "@({})", + x.into_iter() + .map(|v| val_to_pwsh_param(v)) + .collect::>() + .join(",") + ), + serde_json::Value::Object(x) => { + let str = serde_json::to_string(&x).unwrap_or_else(|_| "{}".to_string()); + let escaped = str.replace("'", "''"); + format!("(ConvertFrom-Json '{escaped}')") + } + serde_json::Value::Null => "$null".to_string(), + serde_json::Value::Bool(x) => format!("${x}"), + serde_json::Value::String(x) => { + let escaped = x.replace("'", "''"); + format!("'{escaped}'") + } + serde_json::Value::Number(x) => x.to_string(), + } +} + +fn raw_to_pwsh_param(x: &str) -> String { match serde_json::from_str::(x) { - Ok(serde_json::Value::String(x)) => x, - Ok(x) => serde_json::to_string(&x).unwrap_or_else(|_| String::new()), - _ => String::new(), + Ok(v) => val_to_pwsh_param(v), + Err(e) => { + tracing::error!("Error converting JSON to string: {:?}", e); + "$null".to_string() + } } } @@ -622,17 +656,16 @@ pub async fn handle_powershell_job( .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), + job_args.and_then(|x| x.get(&arg.name).map(|x| raw_to_pwsh_param(x.get()))), ) }) - .collect::>(); + .collect::)>>(); + args_owned - .iter() - .map(|(n, v)| vec![format!("--{n}"), format!("{v}")]) - .flatten() + .into_iter() + .filter_map(|(n, v)| v.map(|v| format!("-{n} {v}"))) .collect::>() + .join(" ") }; #[cfg(windows)] @@ -785,26 +818,16 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", 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\ + "$ErrorActionPreference = 'Stop'\n\ $pipe = New-TemporaryFile\n\ - & \"{}\" -File ./main.ps1 @args 2>&1 | Tee-Object -FilePath $pipe\n\ + ./main.ps1 {pwsh_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() + exit $LASTEXITCODE\n" ), )?; @@ -835,14 +858,13 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", .replace("{SHARED_MOUNT}", shared_mount) .replace("{CACHE_DIR}", POWERSHELL_CACHE_DIR), )?; - let mut cmd_args = vec![ + let cmd_args = vec![ "--config", "run.config.proto", "--", - BIN_BASH.as_str(), - "wrapper.sh", + POWERSHELL_PATH.as_str(), + "wrapper.ps1", ]; - cmd_args.extend(pwsh_args.iter().map(|x| x.as_str())); let mut cmd = Command::new(NSJAIL_PATH.as_str()); cmd.current_dir(job_dir) .env_clear() @@ -857,21 +879,17 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", start_child_process(cmd, NSJAIL_PATH.as_str(), false).await? } else { - let mut cmd; - let mut cmd_args; + let mut cmd = Command::new(POWERSHELL_PATH.as_str()); + let 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()); + cmd_args = vec!["wrapper.ps1"]; } #[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_args = vec![r".\wrapper.ps1"]; } cmd.current_dir(job_dir) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 17e329ec20..0655b11329 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -80,7 +80,7 @@ "windmill-parser-wasm-nu": "1.510.1", "windmill-parser-wasm-php": "1.510.1", "windmill-parser-wasm-py": "1.538.0", - "windmill-parser-wasm-regex": "1.512.0", + "windmill-parser-wasm-regex": "1.552.1", "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.510.1", "windmill-parser-wasm-ts": "1.538.0", @@ -13512,9 +13512,9 @@ "integrity": "sha512-s+bdIgT/fA5em3zYUwF8D14uA/dZh7iu0krZYZQqZUO7txN37hwSCVfovbMkIwm4zPbsJ50mU8DRLt7UpAPZIw==" }, "node_modules/windmill-parser-wasm-regex": { - "version": "1.512.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.512.0.tgz", - "integrity": "sha512-mOkuspfjPPhGZwmerBlFOjRKHjycrlzZUpxO7gHy5D6kwKI2bSz+VI3TsJqmK1y9PwAGgO8JnwRmNAR/YLUkzA==" + "version": "1.552.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.552.1.tgz", + "integrity": "sha512-TIt3+aR1hrUKuMbLAecT0c3Wdcg6w46Jm0EweMZfeqGX7lDp7SK8Ak1vwmzgBiZMJaM98RNAgyTCZHJiUoAeMA==" }, "node_modules/windmill-parser-wasm-ruby": { "version": "1.526.1", diff --git a/frontend/package.json b/frontend/package.json index ab7df58931..a88838b00e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -145,7 +145,7 @@ "windmill-parser-wasm-nu": "1.510.1", "windmill-parser-wasm-php": "1.510.1", "windmill-parser-wasm-py": "1.538.0", - "windmill-parser-wasm-regex": "1.512.0", + "windmill-parser-wasm-regex": "1.552.1", "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.510.1", "windmill-parser-wasm-ts": "1.538.0", @@ -542,4 +542,4 @@ "@rollup/rollup-linux-x64-gnu": "^4.35.0", "fsevents": "^2.3.3" } -} \ No newline at end of file +} diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index 89fdab1d3b..f2a84c034e 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -987,7 +987,7 @@ docker pull $IMAGE docker run --name $WM_JOB_ID -it -d $IMAGE $COMMAND ` -const POWERSHELL_INIT_CODE = `param($Msg, $Dflt = "default value", [int]$Nb = 3) +const POWERSHELL_INIT_CODE = `param($Msg, [string[]]$Names, [PSCustomObject]$Obj, $Dflt = "default value", [int]$Nb = 3) # Import-Module MyModule @@ -1251,8 +1251,8 @@ export const INITIAL_CODE = { }, ruby: { script: RUBY_INIT_CODE - }, - // for related places search: ADD_NEW_LANG + } + // for related places search: ADD_NEW_LANG } export function isInitialCode(content: string): boolean { @@ -1362,7 +1362,7 @@ export function initialCode( return INITIAL_CODE.java.script } else if (language == 'ruby') { return INITIAL_CODE.ruby.script - // for related places search: ADD_NEW_LANG + // for related places search: ADD_NEW_LANG } else if (language == 'bun' || language == 'bunnative') { if (kind == 'trigger') { return INITIAL_CODE.bun.trigger