mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
feat(backend): array and object params support in pwsh (#6706)
* feat(backend): array and object params support in pwsh * add bool and improve arg conversion * fix bash * update parser * sqlx
This commit is contained in:
+23
@@ -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"
|
||||
}
|
||||
+23
@@ -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"
|
||||
}
|
||||
+23
@@ -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"
|
||||
}
|
||||
@@ -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<MainArgSignature> {
|
||||
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<Option<Vec<Arg>>> {
|
||||
@@ -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<Option<Vec<Arg>>> {
|
||||
let param_wrapper = RE_POWERSHELL_PARAM.captures(code);
|
||||
let mut args = vec![];
|
||||
@@ -136,12 +148,12 @@ fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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::<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_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::<Vec<_>>()
|
||||
.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::<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(),
|
||||
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::<Vec<(String, String)>>();
|
||||
.collect::<Vec<(String, Option<String>)>>();
|
||||
|
||||
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::<Vec<_>>()
|
||||
.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)
|
||||
|
||||
Generated
+4
-4
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user