Compare commits
28 Commits
rf/pythonI
...
win-444-ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a16e3f337 | ||
|
|
d311592c5a | ||
|
|
16712bd1c8 | ||
|
|
e688ec6104 | ||
|
|
02dccdb773 | ||
|
|
5d7240e2ba | ||
|
|
40344316d1 | ||
|
|
a3fad2cdcf | ||
|
|
8a6a879948 | ||
|
|
e1d373767f | ||
|
|
f3a4031e0a | ||
|
|
5575899aa3 | ||
|
|
ac6b937b42 | ||
|
|
7befa69e05 | ||
|
|
94f455690c | ||
|
|
dd04566f32 | ||
|
|
6bae455f86 | ||
|
|
4d541d7ae6 | ||
|
|
e66ee4f5a1 | ||
|
|
dc055604d8 | ||
|
|
044ce292b3 | ||
|
|
935a915617 | ||
|
|
07f08cb9ec | ||
|
|
dc8b2b02a5 | ||
|
|
48da52568e | ||
|
|
c322c3400c | ||
|
|
cd2791d09e | ||
|
|
f94fed5ee7 |
4
.github/DockerfileBackendTests
vendored
4
.github/DockerfileBackendTests
vendored
@@ -52,4 +52,6 @@ RUN unzip deno.zip && rm deno.zip && mv deno /usr/bin/deno
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y postgresql-client --allow-unauthenticated
|
||||
|
||||
RUN rustup component add rustfmt
|
||||
RUN rustup component add rustfmt
|
||||
COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk
|
||||
RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet
|
||||
|
||||
4
.github/workflows/build-publish-rh-image.yml
vendored
4
.github/workflows/build-publish-rh-image.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka,php,mysql
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka,php,mysql,csharp
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka,php,mysql
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka,php,mysql,csharp
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
|
||||
2
.github/workflows/build-staging-image.yml
vendored
2
.github/workflows/build-staging-image.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka,php,mysql
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka,php,mysql,csharp
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
|
||||
2
.github/workflows/build_windows_worker_.yml
vendored
2
.github/workflows/build_windows_worker_.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,php,mysql
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,php,mysql,csharp
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
2
.github/workflows/docker-image-rpi4.yml
vendored
2
.github/workflows/docker-image-rpi4.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,deno_core,php,mysql
|
||||
features=embedding,parquet,openidconnect,deno_core,php,mysql,csharp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
|
||||
6
.github/workflows/docker-image.yml
vendored
6
.github/workflows/docker-image.yml
vendored
@@ -76,7 +76,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core,dind,php,mysql
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core,dind,php,mysql,csharp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,otel,dind,php,mysql
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,otel,dind,php,mysql,csharp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,otel,dind,php,mysql
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,otel,dind,php,mysql,csharp
|
||||
PYTHON_IMAGE=python:3.12.2-slim-bookworm
|
||||
tags: |
|
||||
${{ steps.meta-ee-public-py312.outputs.tags }}
|
||||
|
||||
2
.github/workflows/publish_windows_worker.yml
vendored
2
.github/workflows/publish_windows_worker.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,php,mysql
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka,php,mysql,csharp
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
42
backend/Cargo.lock
generated
42
backend/Cargo.lock
generated
@@ -10024,6 +10024,34 @@ dependencies = [
|
||||
"tracing-serde 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-c-sharp"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04c0f6d2209a3cd6d0bb9d2934715da15a15710d3c09c7c1ecd4c9804c3ecd10"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-language"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600"
|
||||
|
||||
[[package]]
|
||||
name = "triomphe"
|
||||
version = "0.1.14"
|
||||
@@ -11037,6 +11065,18 @@ dependencies = [
|
||||
"windmill-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.437.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
"tree-sitter",
|
||||
"tree-sitter-c-sharp",
|
||||
"wasm-bindgen",
|
||||
"windmill-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.437.1"
|
||||
@@ -11159,6 +11199,7 @@ dependencies = [
|
||||
"wasm-bindgen-test",
|
||||
"windmill-parser",
|
||||
"windmill-parser-bash",
|
||||
"windmill-parser-csharp",
|
||||
"windmill-parser-go",
|
||||
"windmill-parser-graphql",
|
||||
"windmill-parser-php",
|
||||
@@ -11290,6 +11331,7 @@ dependencies = [
|
||||
"windmill-git-sync",
|
||||
"windmill-parser",
|
||||
"windmill-parser-bash",
|
||||
"windmill-parser-csharp",
|
||||
"windmill-parser-go",
|
||||
"windmill-parser-graphql",
|
||||
"windmill-parser-php",
|
||||
|
||||
@@ -21,6 +21,7 @@ members = [
|
||||
"./parsers/windmill-parser-wasm",
|
||||
"./parsers/windmill-parser-go",
|
||||
"./parsers/windmill-parser-rust",
|
||||
"./parsers/windmill-parser-csharp",
|
||||
"./parsers/windmill-parser-bash",
|
||||
"./parsers/windmill-parser-py",
|
||||
"./parsers/windmill-parser-py-imports",
|
||||
@@ -67,6 +68,7 @@ otel = ["windmill-common/otel", "windmill-worker/otel"]
|
||||
dind = ["windmill-worker/dind"]
|
||||
php = ["windmill-worker/php"]
|
||||
mysql = ["windmill-worker/mysql"]
|
||||
csharp = ["windmill-worker/csharp"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -131,6 +133,7 @@ windmill-parser-py-imports = { path = "./parsers/windmill-parser-py-imports" }
|
||||
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
|
||||
windmill-parser-rust = { path = "./parsers/windmill-parser-rust" }
|
||||
windmill-parser-yaml = { path = "./parsers/windmill-parser-yaml" }
|
||||
windmill-parser-csharp = { path = "./parsers/windmill-parser-csharp" }
|
||||
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
|
||||
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
|
||||
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
|
||||
@@ -314,3 +317,5 @@ quote = "1.0.36"
|
||||
regex-lite = "0.1.6"
|
||||
yaml-rust = "0.4.5"
|
||||
tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] }
|
||||
tree-sitter = {version = "0.23.0", features = []}
|
||||
tree-sitter-c-sharp = "0.23.0"
|
||||
|
||||
@@ -1 +1 @@
|
||||
dddc8d60d483a2ce8d78233a25a3899a2b1224ca
|
||||
dddc8d60d483a2ce8d78233a25a3899a2b1224ca
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'csharp';
|
||||
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["csharp"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible"]}'::jsonb AND NOT config->'worker_tags' @> '"csharp"'::jsonb;
|
||||
21
backend/parsers/windmill-parser-csharp/Cargo.toml
Normal file
21
backend/parsers/windmill-parser-csharp/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "windmill-parser-csharp"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_parser_csharp"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-parser.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-c-sharp.workspace = true
|
||||
anyhow.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
serde_json.workspace = true
|
||||
# convert_case.workspace = true
|
||||
# lazy_static.workspace = true
|
||||
# regex.workspace = true
|
||||
|
||||
314
backend/parsers/windmill-parser-csharp/src/lib.rs
Normal file
314
backend/parsers/windmill-parser-csharp/src/lib.rs
Normal file
@@ -0,0 +1,314 @@
|
||||
#![cfg_attr(target_arch = "wasm32", feature(c_variadic))]
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_libc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use tree_sitter::Node;
|
||||
use windmill_parser::Arg;
|
||||
use windmill_parser::MainArgSignature;
|
||||
use windmill_parser::Typ;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CsharpMainSigMeta {
|
||||
pub is_async: bool,
|
||||
pub is_public: bool,
|
||||
pub returns_void: bool,
|
||||
pub class_name: Option<String>,
|
||||
pub main_sig: MainArgSignature,
|
||||
}
|
||||
|
||||
fn csharp_param_default_value<'a>(def: Node<'a>, code: &str) -> Option<serde_json::Value> {
|
||||
def.utf8_text(code.as_bytes())
|
||||
.ok()
|
||||
.and_then(|content| serde_json::from_str(content).ok())
|
||||
}
|
||||
|
||||
pub fn parse_csharp_sig_meta(code: &str) -> anyhow::Result<CsharpMainSigMeta> {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
let language = tree_sitter_c_sharp::LANGUAGE;
|
||||
parser
|
||||
.set_language(&language.into())
|
||||
.map_err(|e| anyhow!("Error setting c# as language: {e}"))?;
|
||||
|
||||
// Parse code
|
||||
let tree = parser.parse(code, None).expect("Failed to parse code");
|
||||
let root_node = tree.root_node();
|
||||
|
||||
// Traverse the AST to find the Main method signature
|
||||
let main_sig = find_main_signature(root_node, code);
|
||||
let no_main_func = Some(main_sig.is_none());
|
||||
let mut is_async = false;
|
||||
let mut is_public = false;
|
||||
let mut returns_void = false;
|
||||
let mut class_name = None;
|
||||
|
||||
let mut args = vec![];
|
||||
if let Some((sig, name)) = main_sig {
|
||||
class_name = name;
|
||||
for sig_node in sig.children(&mut sig.walk()) {
|
||||
if sig_node.kind() == "modifier" && sig_node.utf8_text(code.as_bytes())? == "async" {
|
||||
is_async = true;
|
||||
}
|
||||
if sig_node.kind() == "modifier" && sig_node.utf8_text(code.as_bytes())? == "public" {
|
||||
is_public = true;
|
||||
}
|
||||
}
|
||||
if let Some(return_type) = sig.child_by_field_name("returns") {
|
||||
let return_type = return_type.utf8_text(code.as_bytes())?;
|
||||
|
||||
if return_type == "void" || (is_async && return_type == "Task") {
|
||||
returns_void = true;
|
||||
}
|
||||
}
|
||||
if let Some(param_list) = sig.child_by_field_name("parameters") {
|
||||
for p_list_node in param_list.children(&mut param_list.walk()) {
|
||||
if p_list_node.kind() == "parameter" {
|
||||
let mut default = None;
|
||||
for a in p_list_node.children(&mut p_list_node.walk()) {
|
||||
if a.kind() == "=" {
|
||||
if let Some(node) = a.next_sibling() {
|
||||
default = csharp_param_default_value(node, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
let (otyp, typ, name) = parse_csharp_typ(p_list_node, code)?;
|
||||
args.push(Arg { name, otyp, typ, default, has_default: false, oidx: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let main_sig = MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args,
|
||||
has_preprocessor: None,
|
||||
no_main_func,
|
||||
};
|
||||
|
||||
Ok(CsharpMainSigMeta { is_async, returns_void, class_name, main_sig, is_public })
|
||||
}
|
||||
|
||||
pub fn parse_csharp_signature(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
Ok(parse_csharp_sig_meta(code)?.main_sig)
|
||||
}
|
||||
|
||||
fn find_typ<'a>(typ_node: Node<'a>, code: &str) -> anyhow::Result<Typ> {
|
||||
match typ_node.kind() {
|
||||
"predefined_type" => {
|
||||
match typ_node.utf8_text(code.as_bytes()) {
|
||||
Ok("string") => Ok(Typ::Str(None)),
|
||||
Ok("sbyte") | Ok("System.SByte") => Ok(Typ::Bytes),
|
||||
Ok("byte") | Ok("System.Byte") => Ok(Typ::Bytes),
|
||||
Ok("short") | Ok("System.Int16") => Ok(Typ::Int),
|
||||
Ok("ushort") | Ok("System.UInt16") => Ok(Typ::Int),
|
||||
Ok("int") | Ok("System.Int32") => Ok(Typ::Int),
|
||||
Ok("uint") | Ok("System.UInt32") => Ok(Typ::Int),
|
||||
Ok("long") | Ok("System.Int64") => Ok(Typ::Int),
|
||||
Ok("ulong") | Ok("System.UInt64") => Ok(Typ::Int),
|
||||
Ok("char") | Ok("System.Char") => Ok(Typ::Str(None)),
|
||||
Ok("float") | Ok("System.Single") => Ok(Typ::Float),
|
||||
Ok("double") | Ok("System.Double") => Ok(Typ::Float),
|
||||
Ok("bool") | Ok("System.Boolean") => Ok(Typ::Bool),
|
||||
Ok("decimal") | Ok("System.Decimal") => Ok(Typ::Float),
|
||||
Ok("object") => Ok(Typ::Object(vec![])), // TODO: Complete the object type
|
||||
Ok(s) => Err(anyhow!("Unknown type `{s}`")),
|
||||
Err(e) => Err(anyhow!("Error getting type name: {}", e)),
|
||||
}
|
||||
}
|
||||
"array_type" => {
|
||||
let new_typ_node = typ_node
|
||||
.child_by_field_name("type")
|
||||
.ok_or(anyhow!("Failed to find inner type of array type"))?;
|
||||
Ok(Typ::List(Box::new(find_typ(new_typ_node, code)?)))
|
||||
}
|
||||
"identifier" => Ok(Typ::Unknown),
|
||||
"generic_name" => Ok(Typ::Unknown),
|
||||
"pointer_type" => Ok(Typ::Int),
|
||||
"nullable_type" => {
|
||||
let new_typ_node = typ_node
|
||||
.child_by_field_name("type")
|
||||
.ok_or(anyhow!("Failed to find inner type of nullable_type"))?;
|
||||
Ok(find_typ(new_typ_node, code)?)
|
||||
}
|
||||
wc => Err(anyhow!(
|
||||
"Unexpected C# type node kind: {} for '{}'. This type is not handeled by Windmill, please open an issue if this seems to be an error",
|
||||
wc,
|
||||
typ_node.utf8_text(code.as_bytes())?
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_csharp_typ<'a>(
|
||||
param_node: Node<'a>,
|
||||
code: &str,
|
||||
) -> anyhow::Result<(Option<String>, Typ, String)> {
|
||||
let name = param_node
|
||||
.child_by_field_name("name")
|
||||
.and_then(|n| n.utf8_text(code.as_bytes()).ok())
|
||||
.unwrap_or("");
|
||||
let otyp_node = param_node.child_by_field_name("type");
|
||||
let otyp = otyp_node
|
||||
.and_then(|n| n.utf8_text(code.as_bytes()).ok())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let typ = find_typ(otyp_node.unwrap(), code)?;
|
||||
|
||||
Ok((otyp, typ, name.to_string()))
|
||||
}
|
||||
|
||||
// Function to find the Main method's signature
|
||||
fn find_main_signature<'a>(root_node: Node<'a>, code: &str) -> Option<(Node<'a>, Option<String>)> {
|
||||
let mut cursor = root_node.walk();
|
||||
for x in root_node.children(&mut cursor) {
|
||||
if x.kind() == "class_declaration" {
|
||||
let class_name = x
|
||||
.child_by_field_name("name")
|
||||
.and_then(|n| n.utf8_text(code.as_bytes()).ok().map(|s| s.to_string()));
|
||||
for c in x.children(&mut x.walk()) {
|
||||
if c.kind() == "declaration_list" {
|
||||
for w in c.children(&mut c.walk()) {
|
||||
if w.kind() == "method_declaration" {
|
||||
for child in w.children(&mut w.walk()) {
|
||||
if child
|
||||
.utf8_text(code.as_bytes())
|
||||
.map(|name| name == "Main")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Some((w, class_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn parse_csharp_reqs(code: &str) -> (Vec<(String, Option<String>)>, Vec<usize>) {
|
||||
let mut nuget_reqs = Vec::new();
|
||||
let mut pkg_lines = Vec::new();
|
||||
|
||||
for (i, line) in code.split("\n").enumerate() {
|
||||
if line.starts_with('#') {
|
||||
if let Some(req) = parse_nuget_req(&line) {
|
||||
pkg_lines.push(i);
|
||||
nuget_reqs.push(req);
|
||||
}
|
||||
} else {
|
||||
break; // Stop processing after the first non-comment line
|
||||
}
|
||||
}
|
||||
|
||||
(nuget_reqs, pkg_lines)
|
||||
}
|
||||
|
||||
fn parse_nuget_req(line: &str) -> Option<(String, Option<String>)> {
|
||||
// Check if the line starts with `#r "nuget:`
|
||||
if let Some(start) = line.find("#r \"nuget:") {
|
||||
// Extract the content after `#r "nuget:`
|
||||
let start_idx = start + 10;
|
||||
let end_idx = line[start_idx..].find('"')?;
|
||||
let line = &line[start_idx..start_idx + end_idx];
|
||||
let mut splitted = line.split(",");
|
||||
if let Some(pkg) = splitted.next() {
|
||||
return Some((
|
||||
pkg.trim().to_string(),
|
||||
splitted.next().map(|s| s.trim().to_string()),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_parse_csharp_sig_and_meta() {
|
||||
let code = r#"
|
||||
using System;
|
||||
|
||||
class LilProgram
|
||||
{
|
||||
|
||||
public async static string Main(string myString = "World", int myInt = 2, string[] jj = ["asd", "ss"])
|
||||
{
|
||||
Console.Writeline("Hello!!");
|
||||
return "yeah";
|
||||
}
|
||||
|
||||
}"#;
|
||||
let sig_meta = parse_csharp_sig_meta(code).unwrap();
|
||||
|
||||
assert_eq!(sig_meta.class_name, Some("LilProgram".to_string()));
|
||||
assert_eq!(sig_meta.is_async, true);
|
||||
|
||||
let ret = sig_meta.main_sig;
|
||||
assert_eq!(ret.args.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_csharp_sig() {
|
||||
let code = r#"
|
||||
using System;
|
||||
|
||||
class LilProgram
|
||||
{
|
||||
|
||||
public static string Main(string myString = "World", int myInt, string[] jj)
|
||||
{
|
||||
Console.Writeline("Hello!!");
|
||||
return "yeah";
|
||||
}
|
||||
|
||||
}"#;
|
||||
let ret = parse_csharp_signature(code).unwrap();
|
||||
|
||||
assert_eq!(ret.args.len(), 3);
|
||||
|
||||
assert_eq!(ret.args[0].name, "myString");
|
||||
assert_eq!(ret.args[0].otyp, Some("string".to_string()));
|
||||
assert_eq!(ret.args[0].typ, Typ::Str(None));
|
||||
|
||||
assert_eq!(ret.args[1].name, "myInt");
|
||||
assert_eq!(ret.args[1].otyp, Some("int".to_string()));
|
||||
assert_eq!(ret.args[1].typ, Typ::Int);
|
||||
|
||||
assert_eq!(ret.args[2].name, "jj");
|
||||
assert_eq!(ret.args[2].otyp, Some("string[]".to_string()));
|
||||
assert_eq!(ret.args[2].typ, Typ::List(Box::new(Typ::Str(None))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_csharp_reqs() {
|
||||
let file_content = r#"#r "nuget: AutoMapper, 6.1.0"
|
||||
#r "nuget: Newtonsoft.Json, 13.0.1"
|
||||
#r "nuget: Serilog, 2.10.0"
|
||||
|
||||
#r "nuget: Serilog, 2.10.0"
|
||||
|
||||
using System;
|
||||
"#;
|
||||
|
||||
let requirements = parse_csharp_reqs(file_content).0;
|
||||
|
||||
assert_eq!(requirements.len(), 3);
|
||||
assert_eq!(
|
||||
requirements[0],
|
||||
("AutoMapper".to_string(), Some("6.1.0".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
requirements[1],
|
||||
("Newtonsoft.Json".to_string(), Some("13.0.1".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
requirements[2],
|
||||
("Serilog".to_string(), Some("2.10.0".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
208
backend/parsers/windmill-parser-csharp/src/wasm_libc.rs
Normal file
208
backend/parsers/windmill-parser-csharp/src/wasm_libc.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use std::{
|
||||
alloc::{self, Layout},
|
||||
ffi::{c_char, c_int, c_void},
|
||||
mem::align_of,
|
||||
ptr,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/* -------------------------------- stdlib.h -------------------------------- */
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn abort() {
|
||||
panic!("Aborted from C");
|
||||
}
|
||||
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string()) })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(a: &str);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
|
||||
if size == 0 {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let (layout, offset_to_data) = layout_for_size_prepended(size);
|
||||
let buf = alloc::alloc(layout);
|
||||
store_layout(buf, layout, offset_to_data)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
|
||||
if count == 0 || size == 0 {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let (layout, offset_to_data) = layout_for_size_prepended(size * count);
|
||||
let buf = alloc::alloc_zeroed(layout);
|
||||
store_layout(buf, layout, offset_to_data)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn realloc(buf: *mut c_void, new_size: usize) -> *mut c_void {
|
||||
if buf.is_null() {
|
||||
malloc(new_size)
|
||||
} else if new_size == 0 {
|
||||
free(buf);
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
let (old_buf, old_layout) = retrieve_layout(buf);
|
||||
let (new_layout, offset_to_data) = layout_for_size_prepended(new_size);
|
||||
let new_buf = alloc::realloc(old_buf, old_layout, new_layout.size());
|
||||
store_layout(new_buf, new_layout, offset_to_data)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free(buf: *mut c_void) {
|
||||
if buf.is_null() {
|
||||
return;
|
||||
}
|
||||
let (buf, layout) = retrieve_layout(buf);
|
||||
alloc::dealloc(buf, layout);
|
||||
}
|
||||
|
||||
|
||||
// In all these allocations, we store the layout before the data for later retrieval.
|
||||
// This is because we need to know the layout when deallocating the memory.
|
||||
// Here are some helper methods for that:
|
||||
|
||||
/// Given a pointer to the data, retrieve the layout and the pointer to the layout.
|
||||
unsafe fn retrieve_layout(buf: *mut c_void) -> (*mut u8, Layout) {
|
||||
let (_, layout_offset) = Layout::new::<Layout>()
|
||||
.extend(Layout::from_size_align(0, align_of::<*const u8>() * 2).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let buf = (buf as *mut u8).offset(-(layout_offset as isize));
|
||||
let layout = *(buf as *mut Layout);
|
||||
|
||||
(buf, layout)
|
||||
}
|
||||
|
||||
/// Calculate a layout for a given size with space for storing a layout at the start.
|
||||
/// Returns the layout and the offset to the data.
|
||||
fn layout_for_size_prepended(size: usize) -> (Layout, usize) {
|
||||
Layout::new::<Layout>()
|
||||
.extend(Layout::from_size_align(size, align_of::<*const u8>() * 2).unwrap())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Store a layout in the pointer, returning a pointer to where the data should be stored.
|
||||
unsafe fn store_layout(buf: *mut u8, layout: Layout, offset_to_data: usize) -> *mut c_void {
|
||||
*(buf as *mut Layout) = layout;
|
||||
(buf as *mut u8).offset(offset_to_data as isize) as *mut c_void
|
||||
}
|
||||
|
||||
/* -------------------------------- string.h -------------------------------- */
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn strncmp(ptr1: *const c_void, ptr2: *const c_void, n: usize) -> c_int {
|
||||
let s1 = std::slice::from_raw_parts(ptr1 as *const u8, n);
|
||||
let s2 = std::slice::from_raw_parts(ptr2 as *const u8, n);
|
||||
|
||||
for (a, b) in s1.iter().zip(s2.iter()) {
|
||||
if *a != *b || *a == 0 {
|
||||
return (*a as i32) - (*b as i32);
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
/* -------------------------------- wctype.h -------------------------------- */
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn iswspace(c: c_int) -> bool {
|
||||
char::from_u32(c as u32).map_or(false, |c| c.is_whitespace())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn iswalnum(c: c_int) -> bool {
|
||||
char::from_u32(c as u32).map_or(false, |c| c.is_alphanumeric())
|
||||
}
|
||||
|
||||
/* --------------------------------- time.h --------------------------------- */
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn clock() -> u64 {
|
||||
panic!("clock is not supported");
|
||||
}
|
||||
|
||||
/* --------------------------------- ctype.h -------------------------------- */
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn isprint(c: c_int) -> bool {
|
||||
c >= 32 && c <= 126
|
||||
}
|
||||
|
||||
/* --------------------------------- stdio.h -------------------------------- */
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fprintf(_file: *mut c_void, _format: *const c_void, _args: ...) -> c_int {
|
||||
panic!("fprintf is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fputs(_s: *const c_void, _file: *mut c_void) -> c_int {
|
||||
panic!("fputs is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fputc(_c: c_int, _file: *mut c_void) -> c_int {
|
||||
panic!("fputc is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fdopen(_fd: c_int, _mode: *const c_void) -> *mut c_void {
|
||||
panic!("fdopen is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int {
|
||||
panic!("fclose is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fwrite(
|
||||
_ptr: *const c_void,
|
||||
_size: usize,
|
||||
_nmemb: usize,
|
||||
_stream: *mut c_void,
|
||||
) -> usize {
|
||||
panic!("fwrite is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vsnprintf(
|
||||
_buf: *mut c_char,
|
||||
_size: usize,
|
||||
_format: *const c_char,
|
||||
_args: ...
|
||||
) -> c_int {
|
||||
panic!("vsnprintf is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn clock_gettime(ptr: usize, new_size: usize) {
|
||||
panic!("asdasd");
|
||||
}
|
||||
|
||||
// int snprintf( char* restrict buffer, size_t bufsz, const char* restrict format, ... );
|
||||
#[no_mangle]
|
||||
pub extern "C" fn snprintf() {
|
||||
panic!("snprintf is not supported");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn __assert_fail(_: *const i32, _: *const i32, _: *const i32, _: *const i32) {
|
||||
panic!("oh no");
|
||||
}
|
||||
@@ -26,6 +26,7 @@ php-parser = [ "dep:windmill-parser-php"]
|
||||
rust-parser = [ "dep:windmill-parser-rust"]
|
||||
graphql-parser = [ "dep:windmill-parser-graphql"]
|
||||
ansible-parser = [ "dep:windmill-parser-yaml"]
|
||||
csharp-parser = [ "dep:windmill-parser-csharp"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -39,6 +40,7 @@ windmill-parser-php = { workspace = true, optional = true }
|
||||
windmill-parser-graphql = { workspace = true, optional = true }
|
||||
windmill-parser-rust = { workspace = true, optional = true }
|
||||
windmill-parser-yaml = { workspace = true, optional = true }
|
||||
windmill-parser-csharp = { workspace = true, optional = true }
|
||||
wasm-bindgen.workspace = true
|
||||
serde_json.workspace = true
|
||||
getrandom = { workspace = true, features = ["js"] }
|
||||
|
||||
@@ -48,3 +48,9 @@ OUT_DIR="pkg-yaml"
|
||||
wasm-pack build --release --target web --out-dir $OUT_DIR --features "ansible-parser" \
|
||||
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
|
||||
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json
|
||||
|
||||
# C# (needs some more stuff to compile C tree sitter into wasm)
|
||||
# TODO: hasn't been tested on mac, might need fixing
|
||||
OUT_DIR="pkg-csharp"
|
||||
CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target web --out-dir $OUT_DIR --features "csharp-parser"
|
||||
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-csharp"/' $OUT_DIR/package.json
|
||||
|
||||
@@ -48,3 +48,8 @@ OUT_DIR="pkg-yaml"
|
||||
wasm-pack build --release --target web --out-dir $OUT_DIR --features "ansible-parser" \
|
||||
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
|
||||
sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json
|
||||
|
||||
# C# (needs some more stuff to compile C tree sitter into wasm)
|
||||
OUT_DIR="pkg-csharp"
|
||||
CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target web --out-dir $OUT_DIR --features "csharp-parser"
|
||||
sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-csharp"/' $OUT_DIR/package.json
|
||||
|
||||
@@ -24,3 +24,6 @@ popd
|
||||
|
||||
pushd "pkg-yaml" && npm publish ${args}
|
||||
popd
|
||||
|
||||
pushd "pkg-csharp" && npm publish ${args}
|
||||
popd
|
||||
|
||||
@@ -135,3 +135,9 @@ pub fn parse_rust(code: &str) -> String {
|
||||
pub fn parse_ansible(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_yaml::parse_ansible_sig(code))
|
||||
}
|
||||
|
||||
#[cfg(feature = "csharp-parser")]
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_csharp(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_csharp::parse_csharp_signature(code))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define assert(ignore) ((void)0)
|
||||
#define static_assert(cnd, msg) assert(cnd && msg)
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int isprint(int c);
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define PRId32 "d"
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define bool _Bool
|
||||
#define true 1
|
||||
#define false 0
|
||||
19
backend/parsers/windmill-parser-wasm/wasm-sysroot/stdint.h
Normal file
19
backend/parsers/windmill-parser-wasm/wasm-sysroot/stdint.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
typedef signed char int8_t;
|
||||
typedef short int16_t;
|
||||
typedef long int32_t;
|
||||
typedef long long int64_t;
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned long uint32_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
|
||||
typedef unsigned long size_t;
|
||||
|
||||
typedef unsigned int uintptr_t;
|
||||
|
||||
#define UINT8_MAX 0xff
|
||||
#define UINT16_MAX 0xffff
|
||||
#define UINT32_MAX 0xffffffff
|
||||
19
backend/parsers/windmill-parser-wasm/wasm-sysroot/stdio.h
Normal file
19
backend/parsers/windmill-parser-wasm/wasm-sysroot/stdio.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// just some filler type
|
||||
#define FILE void
|
||||
|
||||
#define stdin NULL
|
||||
#define stdout NULL
|
||||
#define stderr NULL
|
||||
|
||||
int fprintf(FILE *__restrict__, const char *__restrict__, ...);
|
||||
int fputs(const char *__restrict, FILE *__restrict);
|
||||
int fputc(int, FILE *);
|
||||
FILE *fdopen(int, const char *);
|
||||
int fclose(FILE *);
|
||||
|
||||
int vsnprintf(char *s, unsigned long n, const char *format, ...);
|
||||
|
||||
#define sprintf(str, ...) 0
|
||||
#define snprintf(str, len, ...) 0
|
||||
12
backend/parsers/windmill-parser-wasm/wasm-sysroot/stdlib.h
Normal file
12
backend/parsers/windmill-parser-wasm/wasm-sysroot/stdlib.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define NULL ((void*)0)
|
||||
|
||||
void* malloc(size_t size);
|
||||
void* calloc(size_t nmemb, size_t size);
|
||||
void free(void* ptr);
|
||||
void* realloc(void* ptr, size_t size);
|
||||
|
||||
void abort(void);
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
void *memcpy(void *dest, const void *src, unsigned long n);
|
||||
void *memmove(void *dest, const void *src, unsigned long n);
|
||||
void *memset(void *s, int c, unsigned long n);
|
||||
int memcmp(const void *ptr1, const void *ptr2, unsigned long n);
|
||||
int strncmp(const char *s1, const char *s2, unsigned long n);
|
||||
5
backend/parsers/windmill-parser-wasm/wasm-sysroot/time.h
Normal file
5
backend/parsers/windmill-parser-wasm/wasm-sysroot/time.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
typedef unsigned long clock_t;
|
||||
#define CLOCKS_PER_SEC ((clock_t)1000000)
|
||||
clock_t clock(void);
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int dup(int);
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
typedef __WCHAR_TYPE__ wchar_t;
|
||||
typedef __WINT_TYPE__ wint_t;
|
||||
|
||||
int iswspace(wchar_t ch);
|
||||
int iswalnum(wint_t _wc);
|
||||
@@ -8,9 +8,7 @@
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor::{
|
||||
load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config,
|
||||
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
|
||||
send_logs_to_object_store,
|
||||
load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config, reload_nuget_config_setting, reload_timeout_wait_result_setting, send_current_log_file_to_object_store, send_logs_to_object_store
|
||||
};
|
||||
use rand::Rng;
|
||||
use sqlx::{postgres::PgListener, Pool, Postgres};
|
||||
@@ -31,15 +29,7 @@ use windmill_common::ee::{maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICE
|
||||
|
||||
use windmill_common::{
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING,
|
||||
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TIMEOUT_WAIT_RESULT_SETTING
|
||||
},
|
||||
scripts::ScriptLang,
|
||||
stats_ee::schedule_stats,
|
||||
@@ -66,10 +56,10 @@ use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
|
||||
|
||||
use windmill_worker::{
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
|
||||
GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR, POWERSHELL_CACHE_DIR,
|
||||
PY311_CACHE_DIR, RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY311_CACHE_DIR, TMP_LOGS_DIR,
|
||||
UV_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS,
|
||||
DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR, PY311_CACHE_DIR, RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY311_CACHE_DIR,
|
||||
TMP_LOGS_DIR, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -769,6 +759,9 @@ Windmill Community Edition {GIT_VERSION}
|
||||
BUNFIG_INSTALL_SCOPES_SETTING => {
|
||||
reload_bunfig_install_scopes_setting(&db).await
|
||||
},
|
||||
NUGET_CONFIG_SETTING => {
|
||||
reload_nuget_config_setting(&db).await
|
||||
},
|
||||
KEEP_JOB_DIR_SETTING => {
|
||||
load_keep_job_dir(&db).await;
|
||||
},
|
||||
@@ -1023,6 +1016,7 @@ pub async fn run_workers(
|
||||
GO_CACHE_DIR,
|
||||
GO_BIN_CACHE_DIR,
|
||||
RUST_CACHE_DIR,
|
||||
CSHARP_CACHE_DIR,
|
||||
HUB_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR,
|
||||
] {
|
||||
|
||||
@@ -34,15 +34,7 @@ use windmill_common::{
|
||||
error,
|
||||
flow_status::FlowStatusModule,
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
|
||||
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING
|
||||
},
|
||||
indexer::load_indexer_config,
|
||||
jobs::QueuedJob,
|
||||
@@ -63,9 +55,7 @@ use windmill_common::{
|
||||
};
|
||||
use windmill_queue::cancel_job;
|
||||
use windmill_worker::{
|
||||
create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender,
|
||||
SendResult, BUNFIG_INSTALL_SCOPES, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, NPM_CONFIG_REGISTRY,
|
||||
PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY,
|
||||
create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender, SendResult, BUNFIG_INSTALL_SCOPES, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -188,6 +178,7 @@ pub async fn initial_load(
|
||||
reload_pip_index_url_setting(&db).await;
|
||||
reload_npm_config_registry_setting(&db).await;
|
||||
reload_bunfig_install_scopes_setting(&db).await;
|
||||
reload_nuget_config_setting(&db).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,6 +907,16 @@ pub async fn reload_bunfig_install_scopes_setting(db: &DB) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_nuget_config_setting(db: &DB) {
|
||||
reload_option_setting_with_tracing(
|
||||
db,
|
||||
NUGET_CONFIG_SETTING,
|
||||
"NUGET_CONFIG",
|
||||
NUGET_CONFIG.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_retention_period_setting(db: &DB) {
|
||||
if let Err(e) = reload_setting(
|
||||
db,
|
||||
|
||||
@@ -1798,6 +1798,48 @@ fn main(world: String) -> Result<String, String> {
|
||||
assert_eq!(result, serde_json::json!("Hello Hyrule!"));
|
||||
}
|
||||
|
||||
// #[sqlx::test(fixtures("base"))]
|
||||
// async fn test_csharp_job(db: Pool<Postgres>) {
|
||||
// initialize_tracing().await;
|
||||
// let server = ApiServer::start(db.clone()).await;
|
||||
// let port = server.addr.port();
|
||||
//
|
||||
// let content = r#"
|
||||
// using System;
|
||||
//
|
||||
// class Script
|
||||
// {
|
||||
// public static string Main(string world, int b = 2)
|
||||
// {
|
||||
// Console.WriteLine($"Hello {world} - {b}. This is a log line");
|
||||
// return $"Hello {world} - {b}";
|
||||
// }
|
||||
// }
|
||||
// "#
|
||||
// .to_owned();
|
||||
//
|
||||
// let result = RunJob::from(JobPayload::Code(RawCode {
|
||||
// hash: None,
|
||||
// content,
|
||||
// path: None,
|
||||
// lock: None,
|
||||
// language: ScriptLang::CSharp,
|
||||
// custom_concurrency_key: None,
|
||||
// concurrent_limit: None,
|
||||
// concurrency_time_window_s: None,
|
||||
// cache_ttl: None,
|
||||
// dedicated_worker: None,
|
||||
// }))
|
||||
// .arg("world", json!("Arakis"))
|
||||
// .arg("b", json!(3))
|
||||
// .run_until_complete(&db, port)
|
||||
// .await
|
||||
// .json_result()
|
||||
// .unwrap();
|
||||
//
|
||||
// assert_eq!(result, serde_json::json!("Hello Arakis - 3"));
|
||||
// }
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_bash_job(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -10692,6 +10692,7 @@ components:
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
]
|
||||
kind:
|
||||
type: string
|
||||
@@ -10795,6 +10796,7 @@ components:
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
]
|
||||
kind:
|
||||
type: string
|
||||
@@ -11019,6 +11021,7 @@ components:
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
]
|
||||
email:
|
||||
type: string
|
||||
@@ -11141,6 +11144,7 @@ components:
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
]
|
||||
is_skipped:
|
||||
type: boolean
|
||||
@@ -11696,6 +11700,7 @@ components:
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
]
|
||||
tag:
|
||||
type: string
|
||||
@@ -13312,6 +13317,7 @@ components:
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
]
|
||||
required:
|
||||
- raw_code
|
||||
|
||||
@@ -583,6 +583,7 @@ async fn create_script_internal<'c>(
|
||||
|| ns.language == ScriptLang::Deno
|
||||
|| ns.language == ScriptLang::Rust
|
||||
|| ns.language == ScriptLang::Ansible
|
||||
|| ns.language == ScriptLang::CSharp
|
||||
|| ns.language == ScriptLang::Php)
|
||||
{
|
||||
Some(String::new())
|
||||
|
||||
@@ -2364,6 +2364,7 @@ async fn tarball_workspace(
|
||||
ScriptLang::Php => "php",
|
||||
ScriptLang::Rust => "rs",
|
||||
ScriptLang::Ansible => "playbook.yml",
|
||||
ScriptLang::CSharp => "cs",
|
||||
};
|
||||
archive
|
||||
.write_to_archive(&script.content, &format!("{}.{}", script.path, ext))
|
||||
|
||||
@@ -10,6 +10,7 @@ pub const REQUEST_SIZE_LIMIT_SETTING: &str = "request_size_limit_mb";
|
||||
pub const LICENSE_KEY_SETTING: &str = "license_key";
|
||||
pub const NPM_CONFIG_REGISTRY_SETTING: &str = "npm_config_registry";
|
||||
pub const BUNFIG_INSTALL_SCOPES_SETTING: &str = "bunfig_install_scopes";
|
||||
pub const NUGET_CONFIG_SETTING: &str = "nuget_config";
|
||||
|
||||
pub const EXTRA_PIP_INDEX_URL_SETTING: &str = "pip_extra_index_url";
|
||||
pub const PIP_INDEX_URL_SETTING: &str = "pip_index_url";
|
||||
|
||||
@@ -45,6 +45,7 @@ pub enum ScriptLang {
|
||||
Php,
|
||||
Rust,
|
||||
Ansible,
|
||||
CSharp,
|
||||
}
|
||||
|
||||
impl ScriptLang {
|
||||
@@ -67,6 +68,7 @@ impl ScriptLang {
|
||||
ScriptLang::Php => "php",
|
||||
ScriptLang::Rust => "rust",
|
||||
ScriptLang::Ansible => "ansible",
|
||||
ScriptLang::CSharp => "csharp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ lazy_static::lazy_static! {
|
||||
"php".to_string(),
|
||||
"rust".to_string(),
|
||||
"ansible".to_string(),
|
||||
"csharp".to_string(),
|
||||
"dependency".to_string(),
|
||||
"flow".to_string(),
|
||||
"other".to_string()
|
||||
|
||||
@@ -24,6 +24,7 @@ otel = ["windmill-common/otel", "dep:opentelemetry"]
|
||||
dind = ["dep:bollard"]
|
||||
php = ["dep:windmill-parser-php"]
|
||||
mysql = ["dep:mysql_async"]
|
||||
csharp = ["dep:windmill-parser-csharp"]
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
@@ -33,6 +34,7 @@ windmill-parser.workspace = true
|
||||
windmill-parser-ts.workspace = true
|
||||
windmill-parser-go.workspace = true
|
||||
windmill-parser-rust.workspace = true
|
||||
windmill-parser-csharp = { workspace = true, optional = true }
|
||||
windmill-parser-py.workspace = true
|
||||
windmill-parser-yaml.workspace = true
|
||||
windmill-parser-py-imports.workspace = true
|
||||
|
||||
114
backend/windmill-worker/nsjail/run.csharp.config.proto
Normal file
114
backend/windmill-worker/nsjail/run.csharp.config.proto
Normal file
@@ -0,0 +1,114 @@
|
||||
name: "csharp run script"
|
||||
|
||||
mode: ONCE
|
||||
hostname: "csharp"
|
||||
log_level: ERROR
|
||||
|
||||
disable_rl: true
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
clone_newnet: false
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
keep_caps: false
|
||||
keep_env: true
|
||||
mount_proc: true
|
||||
|
||||
mount {
|
||||
src: "/bin"
|
||||
dst: "/bin"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/lib"
|
||||
dst: "/lib"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "/lib64"
|
||||
dst: "/lib64"
|
||||
is_bind: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "/usr"
|
||||
dst: "/usr"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/opt/dotnet-sdk/bin"
|
||||
dst: "/opt/dotnet-sdk/bin"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/null"
|
||||
dst: "/dev/null"
|
||||
is_bind: true
|
||||
rw: true
|
||||
}
|
||||
|
||||
mount {
|
||||
dst: "/tmp"
|
||||
fstype: "tmpfs"
|
||||
rw: true
|
||||
options: "size=500000000"
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/Main"
|
||||
dst: "/tmp/main"
|
||||
is_bind: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/args.json"
|
||||
dst: "/tmp/args.json"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/result.json"
|
||||
dst: "/tmp/result.json"
|
||||
rw: true
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/etc"
|
||||
dst: "/etc"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/random"
|
||||
dst: "/dev/random"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/urandom"
|
||||
dst: "/dev/urandom"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
iface_no_lo: true
|
||||
|
||||
mount {
|
||||
src: "{CACHE_DIR}"
|
||||
dst: "/tmp/.cache/csharp"
|
||||
is_bind: true
|
||||
rw: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
{SHARED_MOUNT}
|
||||
@@ -2,7 +2,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
process::Stdio,
|
||||
};
|
||||
|
||||
@@ -29,8 +29,7 @@ use windmill_queue::{append_logs, CanceledBy};
|
||||
use crate::{
|
||||
bash_executor::BIN_BASH,
|
||||
common::{
|
||||
get_reserved_variables, read_and_check_result, start_child_process, transform_json,
|
||||
OccupancyMetrics,
|
||||
check_executor_binary_exists, get_reserved_variables, read_and_check_result, start_child_process, transform_json, OccupancyMetrics
|
||||
},
|
||||
handle_child::handle_child,
|
||||
python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile},
|
||||
@@ -185,24 +184,6 @@ async fn install_galaxy_collections(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
fn check_ansible_exists() -> Result<(), error::Error> {
|
||||
if !Path::new(ANSIBLE_PLAYBOOK_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find ansible-playbook at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run Ansible jobs.", ANSIBLE_PLAYBOOK_PATH.as_str());
|
||||
return Err(error::Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn check_ansible_exists() -> Result<(), error::Error> {
|
||||
if !Path::new(ANSIBLE_PLAYBOOK_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find ansible-playbook at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-ee-full` for your instance in order to run Ansible jobs.", ANSIBLE_PLAYBOOK_PATH.as_str());
|
||||
return Err(error::Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_ansible_job(
|
||||
requirements_o: Option<String>,
|
||||
job_dir: &str,
|
||||
@@ -219,7 +200,7 @@ pub async fn handle_ansible_job(
|
||||
envs: HashMap<String, String>,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> windmill_common::error::Result<Box<RawValue>> {
|
||||
check_ansible_exists()?;
|
||||
check_executor_binary_exists("ansible-playbook", ANSIBLE_PLAYBOOK_PATH.as_str(), "ansible")?;
|
||||
|
||||
let (logs, reqs, playbook) = windmill_parser_yaml::parse_ansible_reqs(inner_content)?;
|
||||
append_logs(&job.id, &job.workspace_id, logs, db).await;
|
||||
|
||||
@@ -31,7 +31,13 @@ use windmill_common::{
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{variables, DB};
|
||||
|
||||
@@ -53,6 +59,23 @@ pub async fn build_args_map<'a>(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
pub fn check_executor_binary_exists(
|
||||
executor: &str,
|
||||
executor_path: &str,
|
||||
language: &str,
|
||||
) -> Result<(), Error> {
|
||||
if !Path::new(executor_path).exists() {
|
||||
#[cfg(feature = "enterprise")]
|
||||
let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run {language} jobs.", executor_path);
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run {language} jobs.", executor_path);
|
||||
return Err(Error::NotFound(msg));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn build_args_values(
|
||||
job: &QueuedJob,
|
||||
client: &AuthedClientBackgroundTask,
|
||||
|
||||
514
backend/windmill-worker/src/csharp_executor.rs
Normal file
514
backend/windmill-worker/src/csharp_executor.rs
Normal file
@@ -0,0 +1,514 @@
|
||||
use anyhow::anyhow;
|
||||
use serde_json::value::RawValue;
|
||||
use std::{collections::HashMap, io, path::Path, process::Stdio};
|
||||
use uuid::Uuid;
|
||||
#[cfg(feature = "csharp")]
|
||||
use windmill_parser_csharp::parse_csharp_reqs;
|
||||
|
||||
use itertools::Itertools;
|
||||
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
jobs::QueuedJob,
|
||||
utils::calculate_hash,
|
||||
worker::{save_cache, write_file},
|
||||
};
|
||||
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,
|
||||
AuthedClientBackgroundTask, CSHARP_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, DOTNET_PATH,
|
||||
HOME_ENV, NSJAIL_PATH, NUGET_CONFIG, PATH_ENV, TZ_ENV,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::SYSTEM_ROOT;
|
||||
|
||||
const NSJAIL_CONFIG_RUN_CSHARP_CONTENT: &str = include_str!("../nsjail/run.csharp.config.proto");
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HOME_DIR: String = std::env::var("HOME").expect("Could not find the HOME environment variable");
|
||||
}
|
||||
|
||||
const CSHARP_OBJECT_STORE_PREFIX: &str = "csharpbin/";
|
||||
|
||||
#[cfg(feature = "csharp")]
|
||||
pub async fn generate_nuget_lockfile(
|
||||
job_id: &Uuid,
|
||||
code: &str,
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
w_id: &str,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> error::Result<String> {
|
||||
check_executor_binary_exists("dotnet", DOTNET_PATH.as_str(), "C#")?;
|
||||
|
||||
if let Some(nuget_config) = NUGET_CONFIG.read().await.clone() {
|
||||
write_file(job_dir, "nuget.config", &nuget_config)?;
|
||||
}
|
||||
|
||||
let (reqs, lines_to_remove) = parse_csharp_reqs(code);
|
||||
|
||||
gen_cs_proj(code, job_dir, reqs, lines_to_remove)?;
|
||||
|
||||
let mut gen_lockfile_cmd = Command::new(DOTNET_PATH.as_str());
|
||||
gen_lockfile_cmd
|
||||
.current_dir(job_dir)
|
||||
.args(vec!["restore", "--use-lock-file"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
let gen_lockfile_process = start_child_process(gen_lockfile_cmd, DOTNET_PATH.as_str()).await?;
|
||||
handle_child(
|
||||
job_id,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
gen_lockfile_process,
|
||||
false,
|
||||
worker_name,
|
||||
w_id,
|
||||
"dotnet restore",
|
||||
None,
|
||||
false,
|
||||
&mut Some(occupancy_metrics),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Err(e) = std::fs::remove_file(Path::new(job_dir).join("nuget.config")) {
|
||||
if e.kind() != io::ErrorKind::NotFound {
|
||||
Err(anyhow!("Error erasing nuget.config: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
let path_lock = format!("{job_dir}/packages.lock.json");
|
||||
let mut file = File::open(path_lock).await?;
|
||||
let mut req_content = String::new();
|
||||
file.read_to_string(&mut req_content).await?;
|
||||
Ok(req_content)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "csharp"))]
|
||||
pub async fn generate_nuget_lockfile(
|
||||
_job_id: &Uuid,
|
||||
_code: &str,
|
||||
_mem_peak: &mut i32,
|
||||
_canceled_by: &mut Option<CanceledBy>,
|
||||
_job_dir: &str,
|
||||
_db: &sqlx::Pool<sqlx::Postgres>,
|
||||
_worker_name: &str,
|
||||
_w_id: &str,
|
||||
_occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> error::Result<String> {
|
||||
Err(anyhow!("C# is not available because the feature is not enabled").into())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "csharp")]
|
||||
fn gen_cs_proj(
|
||||
code: &str,
|
||||
job_dir: &str,
|
||||
reqs: Vec<(String, Option<String>)>,
|
||||
lines_to_remove: Vec<usize>,
|
||||
) -> anyhow::Result<()> {
|
||||
let code = remove_lines_from_text(code, lines_to_remove);
|
||||
|
||||
let pkgs = reqs
|
||||
.into_iter()
|
||||
.map(|(pkg, vrsion_o)| {
|
||||
let version = vrsion_o
|
||||
.map(|v| format!("Version=\"{v}\""))
|
||||
.unwrap_or("".to_string());
|
||||
format!(" <PackageReference Include=\"{pkg}\" {version}/>")
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
write_file(
|
||||
job_dir,
|
||||
"Main.csproj",
|
||||
&format!(
|
||||
r#"<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<StartupObject>WindmillScriptCSharpInternal.Wrapper</StartupObject>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
{pkgs}
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
"#
|
||||
),
|
||||
)?;
|
||||
|
||||
write_file(job_dir, "Script.cs", code.as_str())?;
|
||||
|
||||
let sig_meta = windmill_parser_csharp::parse_csharp_sig_meta(code.as_str())?;
|
||||
let sig = sig_meta.main_sig;
|
||||
let spread = &sig
|
||||
.args
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|x| format!("parsedArgs.{}", &x.name))
|
||||
.join(", ");
|
||||
let args_class_body = &sig
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
Ok(format!(
|
||||
" public {} {} {{ get; set; }}",
|
||||
x.otyp
|
||||
.ok_or(anyhow!("Type not found for argument {}", x.name))?,
|
||||
&x.name,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<String>, anyhow::Error>>()?
|
||||
.join("\n");
|
||||
|
||||
let class_name = sig_meta.class_name.unwrap_or("Script".to_string());
|
||||
|
||||
let script_call = match (sig_meta.is_async, sig_meta.returns_void) {
|
||||
(true, true) => format!(
|
||||
r#"
|
||||
{class_name}.Main({spread}).Wait();"#
|
||||
),
|
||||
(false, true) => format!(
|
||||
r#"
|
||||
{class_name}.Main({spread});"#
|
||||
),
|
||||
|
||||
(false, false) => format!(
|
||||
r#"
|
||||
var result = {class_name}.Main({spread});
|
||||
|
||||
var jsonResult = JsonSerializer.Serialize(result);
|
||||
|
||||
File.WriteAllText("result.json", jsonResult);"#
|
||||
),
|
||||
(true, false) => format!(
|
||||
r#"
|
||||
var result = {class_name}.Main({spread}).Result;
|
||||
|
||||
var jsonResult = JsonSerializer.Serialize(result);
|
||||
|
||||
File.WriteAllText("result.json", jsonResult);"#
|
||||
),
|
||||
};
|
||||
|
||||
write_file(
|
||||
job_dir,
|
||||
"Wrapper.cs",
|
||||
&format!(
|
||||
r#"using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WindmillScriptCSharpInternal {{
|
||||
|
||||
struct Args {{
|
||||
{args_class_body}
|
||||
}}
|
||||
|
||||
class Wrapper
|
||||
{{
|
||||
static void Main(string[] args)
|
||||
{{
|
||||
using FileStream fs = File.OpenRead("args.json");
|
||||
Args parsedArgs = JsonSerializer.Deserialize<Args>(fs);
|
||||
|
||||
File.WriteAllText("result.json", "null");
|
||||
|
||||
{script_call}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "csharp")]
|
||||
async fn build_cs_proj(
|
||||
job_id: &Uuid,
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
w_id: &str,
|
||||
base_internal_url: &str,
|
||||
hash: &str,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> error::Result<String> {
|
||||
if let Some(nuget_config) = NUGET_CONFIG.read().await.clone() {
|
||||
write_file(job_dir, "nuget.config", &nuget_config)?;
|
||||
}
|
||||
|
||||
let mut build_cs_cmd = Command::new(DOTNET_PATH.as_str());
|
||||
|
||||
build_cs_cmd
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("HOME", HOME_ENV.as_str())
|
||||
.args(vec![
|
||||
"publish",
|
||||
"--configuration",
|
||||
"Release",
|
||||
"-o",
|
||||
job_dir,
|
||||
"--no-self-contained",
|
||||
"-p:PublishSingleFile=true",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
build_cs_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
|
||||
build_cs_cmd.env(
|
||||
"TMP",
|
||||
std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let build_cs_process = start_child_process(build_cs_cmd, DOTNET_PATH.as_str()).await?;
|
||||
handle_child(
|
||||
job_id,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
build_cs_process,
|
||||
false,
|
||||
worker_name,
|
||||
w_id,
|
||||
"dotnet publish",
|
||||
None,
|
||||
false,
|
||||
&mut Some(occupancy_metrics),
|
||||
)
|
||||
.await?;
|
||||
append_logs(job_id, w_id, "\n\n", db).await;
|
||||
|
||||
if let Err(e) = std::fs::remove_file(Path::new(job_dir).join("nuget.config")) {
|
||||
if e.kind() != io::ErrorKind::NotFound {
|
||||
Err(anyhow!("Error erasing nuget.config: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR);
|
||||
|
||||
match save_cache(
|
||||
&bin_path,
|
||||
&format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}"),
|
||||
&format!("{job_dir}/Main"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
let em = format!("could not save {job_dir}/Main to C# cache: {e:?}",);
|
||||
tracing::error!(em);
|
||||
Ok(em)
|
||||
}
|
||||
Ok(logs) => Ok(logs),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_lines_from_text(contents: &str, indices_to_remove: Vec<usize>) -> String {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for (i, line) in contents.lines().enumerate() {
|
||||
if !indices_to_remove.contains(&i) {
|
||||
result.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
result.join("\n")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "csharp"))]
|
||||
pub async fn handle_csharp_job(
|
||||
_mem_peak: &mut i32,
|
||||
_canceled_by: &mut Option<CanceledBy>,
|
||||
_job: &QueuedJob,
|
||||
_db: &sqlx::Pool<sqlx::Postgres>,
|
||||
_client: &AuthedClientBackgroundTask,
|
||||
_inner_content: &str,
|
||||
_job_dir: &str,
|
||||
_requirements_o: Option<String>,
|
||||
_shared_mount: &str,
|
||||
_base_internal_url: &str,
|
||||
_worker_name: &str,
|
||||
_envs: HashMap<String, String>,
|
||||
_occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> Result<Box<RawValue>, Error> {
|
||||
Err(anyhow!("C# is not available because the feature is not enabled").into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "csharp")]
|
||||
pub async fn handle_csharp_job(
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
client: &AuthedClientBackgroundTask,
|
||||
inner_content: &str,
|
||||
job_dir: &str,
|
||||
requirements_o: Option<String>,
|
||||
shared_mount: &str,
|
||||
base_internal_url: &str,
|
||||
worker_name: &str,
|
||||
envs: HashMap<String, String>,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> Result<Box<RawValue>, Error> {
|
||||
check_executor_binary_exists("dotnet", DOTNET_PATH.as_str(), "C#")?;
|
||||
|
||||
let hash = calculate_hash(&format!(
|
||||
"{}{}",
|
||||
inner_content,
|
||||
requirements_o
|
||||
.as_ref()
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default()
|
||||
));
|
||||
let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR);
|
||||
let remote_path = format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}");
|
||||
|
||||
let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await;
|
||||
|
||||
let cache_logs = if cache {
|
||||
let target = format!("{job_dir}/Main");
|
||||
|
||||
#[cfg(unix)]
|
||||
let symlink = std::os::unix::fs::symlink(&bin_path, &target);
|
||||
#[cfg(windows)]
|
||||
let symlink = std::os::windows::fs::symlink_dir(&bin_path, &target);
|
||||
|
||||
symlink.map_err(|e| {
|
||||
Error::ExecutionErr(format!(
|
||||
"could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
cache_logs
|
||||
} else {
|
||||
let logs1 = format!("{cache_logs}\n\n--- DOTNET BUILD ---\n");
|
||||
append_logs(&job.id, &job.workspace_id, logs1, db).await;
|
||||
|
||||
let (reqs, lines_to_remove) = parse_csharp_reqs(inner_content);
|
||||
for req in &reqs {
|
||||
append_logs(
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
format!(
|
||||
"Requirement detected: {} {}\n",
|
||||
req.0,
|
||||
req.1.as_ref().unwrap_or(&"".to_string())
|
||||
),
|
||||
db,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
gen_cs_proj(inner_content, job_dir, reqs, lines_to_remove)?;
|
||||
|
||||
if let Some(reqs) = requirements_o {
|
||||
if !reqs.is_empty() {
|
||||
write_file(job_dir, "packages.lock.json", &reqs)?;
|
||||
}
|
||||
}
|
||||
|
||||
build_cs_proj(
|
||||
&job.id,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_dir,
|
||||
db,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
base_internal_url,
|
||||
&hash,
|
||||
occupancy_metrics,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
create_args_and_out_file(client, job, job_dir, db).await?;
|
||||
|
||||
let logs2 = format!("{cache_logs}\n\n--- C# CODE EXECUTION ---\n");
|
||||
append_logs(&job.id, &job.workspace_id, format!("{}\n", logs2), db).await;
|
||||
|
||||
let client = &client.get_authed().await;
|
||||
let reserved_variables = get_reserved_variables(job, &client.token, db).await?;
|
||||
|
||||
let child = if !*DISABLE_NSJAIL {
|
||||
write_file(
|
||||
job_dir,
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_CSHARP_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CACHE_DIR}", CSHARP_CACHE_DIR)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount),
|
||||
)?;
|
||||
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
nsjail_cmd
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("TZ", TZ_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.args(vec!["--config", "run.config.proto", "--", "/tmp/main"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await?
|
||||
} else {
|
||||
let compiled_executable_name = "./Main";
|
||||
let mut run_csharp = Command::new(compiled_executable_name);
|
||||
run_csharp
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("TZ", TZ_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("HOME", HOME_ENV.as_str())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
start_child_process(run_csharp, compiled_executable_name).await?
|
||||
};
|
||||
|
||||
handle_child(
|
||||
&job.id,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
child,
|
||||
!*DISABLE_NSJAIL,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
"csharp run",
|
||||
job.timeout,
|
||||
false,
|
||||
&mut Some(occupancy_metrics),
|
||||
)
|
||||
.await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
@@ -31,6 +31,7 @@ mod worker;
|
||||
mod worker_flow;
|
||||
mod worker_lockfiles;
|
||||
mod job_logger_ee;
|
||||
mod csharp_executor;
|
||||
pub use worker::*;
|
||||
|
||||
pub use result_processor::handle_job_error;
|
||||
|
||||
@@ -2,7 +2,7 @@ use convert_case::{Case, Casing};
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use serde_json::value::RawValue;
|
||||
use std::{collections::HashMap, path::Path, process::Stdio};
|
||||
use std::{collections::HashMap, process::Stdio};
|
||||
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{
|
||||
@@ -15,8 +15,7 @@ use windmill_queue::{append_logs, CanceledBy};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
create_args_and_out_file, get_main_override, get_reserved_variables, read_result,
|
||||
start_child_process, OccupancyMetrics,
|
||||
check_executor_binary_exists, create_args_and_out_file, get_main_override, get_reserved_variables, read_result, start_child_process, OccupancyMetrics
|
||||
},
|
||||
handle_child::handle_child,
|
||||
AuthedClientBackgroundTask, COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER,
|
||||
@@ -73,7 +72,7 @@ pub async fn composer_install(
|
||||
lock: Option<String>,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> Result<String> {
|
||||
check_php_exists()?;
|
||||
check_executor_binary_exists("php", PHP_PATH.as_str(), "php")?;
|
||||
|
||||
write_file(job_dir, "composer.json", &requirements)?;
|
||||
|
||||
@@ -131,24 +130,6 @@ $args->{arg_name} = new {rt_name}($args->{arg_name});"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
fn check_php_exists() -> error::Result<()> {
|
||||
if !Path::new(PHP_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find php at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run php jobs.", PHP_PATH.as_str());
|
||||
return Err(error::Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn check_php_exists() -> error::Result<()> {
|
||||
if !Path::new(PHP_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find php at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-ee-full` for your instance in order to run php jobs.", PHP_PATH.as_str());
|
||||
return Err(error::Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn handle_php_job(
|
||||
requirements_o: Option<String>,
|
||||
@@ -165,7 +146,7 @@ pub async fn handle_php_job(
|
||||
shared_mount: &str,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> error::Result<Box<RawValue>> {
|
||||
check_php_exists()?;
|
||||
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() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde_json::value::RawValue;
|
||||
use std::{collections::HashMap, path::Path, process::Stdio};
|
||||
use std::{collections::HashMap, process::Stdio};
|
||||
use uuid::Uuid;
|
||||
use windmill_parser_rust::parse_rust_deps_into_manifest;
|
||||
|
||||
@@ -15,8 +15,7 @@ use windmill_queue::{append_logs, CanceledBy};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
create_args_and_out_file, get_reserved_variables, read_result, start_child_process,
|
||||
OccupancyMetrics,
|
||||
check_executor_binary_exists, create_args_and_out_file, get_reserved_variables, read_result, start_child_process, OccupancyMetrics
|
||||
},
|
||||
handle_child::handle_child,
|
||||
AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
|
||||
@@ -132,7 +131,7 @@ pub async fn generate_cargo_lockfile(
|
||||
w_id: &str,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> error::Result<String> {
|
||||
check_cargo_exists()?;
|
||||
check_executor_binary_exists("cargo", CARGO_PATH.as_str(), "rust")?;
|
||||
|
||||
gen_cargo_crate(code, job_dir)?;
|
||||
|
||||
@@ -271,24 +270,6 @@ pub fn compute_rust_hash(code: &str, requirements_o: Option<&String>) -> String
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
fn check_cargo_exists() -> Result<(), Error> {
|
||||
if !Path::new(CARGO_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find cargo at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run rust jobs.", CARGO_PATH.as_str());
|
||||
return Err(Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn check_cargo_exists() -> Result<(), Error> {
|
||||
if !Path::new(CARGO_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find cargo at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-ee-full` for your instance in order to run rust jobs.", CARGO_PATH.as_str());
|
||||
return Err(Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn handle_rust_job(
|
||||
mem_peak: &mut i32,
|
||||
@@ -305,7 +286,7 @@ pub async fn handle_rust_job(
|
||||
envs: HashMap<String, String>,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
) -> Result<Box<RawValue>, Error> {
|
||||
check_cargo_exists()?;
|
||||
check_executor_binary_exists("cargo", CARGO_PATH.as_str(), "rust")?;
|
||||
|
||||
let hash = compute_rust_hash(inner_content, requirements_o.as_ref());
|
||||
let bin_path = format!("{}/{hash}", RUST_CACHE_DIR);
|
||||
|
||||
@@ -97,6 +97,7 @@ use crate::{
|
||||
build_args_map, cached_result_path, get_cached_resource_value_if_valid,
|
||||
get_reserved_variables, update_worker_ping_for_failed_init_script, OccupancyMetrics,
|
||||
},
|
||||
csharp_executor::handle_csharp_job,
|
||||
deno_executor::handle_deno_job,
|
||||
go_executor::handle_go_job,
|
||||
graphql_executor::do_graphql,
|
||||
@@ -268,6 +269,7 @@ pub const DENO_CACHE_DIR_NPM: &str = concatcp!(ROOT_CACHE_DIR, "deno/npm");
|
||||
|
||||
pub const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go");
|
||||
pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust");
|
||||
pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp");
|
||||
pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun");
|
||||
pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun");
|
||||
pub const BUN_DEPSTAR_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "buntar");
|
||||
@@ -370,6 +372,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref POWERSHELL_PATH: String = std::env::var("POWERSHELL_PATH").unwrap_or_else(|_| "/usr/bin/pwsh".to_string());
|
||||
pub static ref PHP_PATH: String = std::env::var("PHP_PATH").unwrap_or_else(|_| "/usr/bin/php".to_string());
|
||||
pub static ref COMPOSER_PATH: String = std::env::var("COMPOSER_PATH").unwrap_or_else(|_| "/usr/bin/composer".to_string());
|
||||
pub static ref DOTNET_PATH: String = std::env::var("DOTNET_PATH").unwrap_or_else(|_| "/usr/bin/dotnet".to_string());
|
||||
pub static ref NSJAIL_PATH: String = std::env::var("NSJAIL_PATH").unwrap_or_else(|_| "nsjail".to_string());
|
||||
pub static ref PATH_ENV: String = std::env::var("PATH").unwrap_or_else(|_| String::new());
|
||||
pub static ref HOME_ENV: String = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||
@@ -384,6 +387,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
pub static ref NPM_CONFIG_REGISTRY: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
pub static ref BUNFIG_INSTALL_SCOPES: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
pub static ref NUGET_CONFIG: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
pub static ref PIP_EXTRA_INDEX_URL: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
pub static ref PIP_INDEX_URL: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
@@ -2710,6 +2714,24 @@ mount {{
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(ScriptLang::CSharp) => {
|
||||
handle_csharp_job(
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job,
|
||||
db,
|
||||
client,
|
||||
&inner_content,
|
||||
job_dir,
|
||||
requirements_o,
|
||||
&shared_mount,
|
||||
base_internal_url,
|
||||
worker_name,
|
||||
envs,
|
||||
occupancy_metrics,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => panic!("unreachable, language is not supported: {language:#?}"),
|
||||
};
|
||||
tracing::info!(
|
||||
|
||||
@@ -28,10 +28,11 @@ use windmill_parser_ts::parse_expr_for_imports;
|
||||
use windmill_queue::{append_logs, CanceledBy, PushIsolationLevel};
|
||||
|
||||
use crate::common::OccupancyMetrics;
|
||||
use crate::csharp_executor::generate_nuget_lockfile;
|
||||
use crate::python_executor::{
|
||||
create_dependencies_dir, handle_python_reqs, uv_pip_compile, USE_PIP_COMPILE, USE_PIP_INSTALL,
|
||||
};
|
||||
use crate::rust_executor::{build_rust_crate, compute_rust_hash, generate_cargo_lockfile};
|
||||
use crate::rust_executor::generate_cargo_lockfile;
|
||||
use crate::{
|
||||
bun_executor::gen_bun_lockfile,
|
||||
deno_executor::generate_deno_lock,
|
||||
@@ -1869,20 +1870,26 @@ async fn capture_dependency_job(
|
||||
)
|
||||
.await?;
|
||||
|
||||
build_rust_crate(
|
||||
Ok(lockfile)
|
||||
}
|
||||
ScriptLang::CSharp => {
|
||||
if raw_deps {
|
||||
return Err(Error::ExecutionErr(
|
||||
"Raw dependencies not supported for C#".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
generate_nuget_lockfile(
|
||||
job_id,
|
||||
job_raw_code,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_dir,
|
||||
db,
|
||||
worker_name,
|
||||
w_id,
|
||||
base_internal_url,
|
||||
&compute_rust_hash(&job_raw_code, Some(&lockfile)),
|
||||
occupancy_metrics,
|
||||
)
|
||||
.await?;
|
||||
Ok(lockfile)
|
||||
).await
|
||||
}
|
||||
ScriptLang::Postgresql => Ok("".to_owned()),
|
||||
ScriptLang::Mysql => Ok("".to_owned()),
|
||||
|
||||
@@ -4,3 +4,6 @@ COPY --from=rust:1.80.1 /usr/local/cargo /usr/local/cargo
|
||||
COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup
|
||||
|
||||
RUN pip3 install ansible
|
||||
|
||||
COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk
|
||||
RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet
|
||||
|
||||
@@ -4,3 +4,6 @@ COPY --from=rust:1.80.1 /usr/local/cargo /usr/local/cargo
|
||||
COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup
|
||||
|
||||
RUN pip3 install ansible
|
||||
|
||||
COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk
|
||||
RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet
|
||||
|
||||
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@@ -69,6 +69,7 @@
|
||||
"vscode-languageclient": "~9.0.1",
|
||||
"vscode-uri": "~3.0.8",
|
||||
"vscode-ws-jsonrpc": "~3.3.2",
|
||||
"windmill-parser-wasm-csharp": "^1.437.1",
|
||||
"windmill-parser-wasm-go": "^1.429.0",
|
||||
"windmill-parser-wasm-php": "^1.429.0",
|
||||
"windmill-parser-wasm-py": "^1.429.0",
|
||||
@@ -142,6 +143,11 @@
|
||||
"svelte": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"../backend/parsers/windmill-parser-wasm/pkg-csharp": {
|
||||
"name": "windmill-parser-wasm-csharp",
|
||||
"version": "1.411.1",
|
||||
"extraneous": true
|
||||
},
|
||||
"../svelte-dnd-action": {
|
||||
"extraneous": true
|
||||
},
|
||||
@@ -13644,6 +13650,11 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-csharp": {
|
||||
"version": "1.437.1",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-csharp/-/windmill-parser-wasm-csharp-1.437.1.tgz",
|
||||
"integrity": "sha512-qzB/kUE9JCf1CYFDz+50AI+SUVaZYn3lSgqmJ11Iuibl41AC4EIvfH4zrsOU53lcTOb9b4ZH3D6z9FjCCfqWsw=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-go": {
|
||||
"version": "1.429.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-go/-/windmill-parser-wasm-go-1.429.0.tgz",
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
"vscode-languageclient": "~9.0.1",
|
||||
"vscode-uri": "~3.0.8",
|
||||
"vscode-ws-jsonrpc": "~3.3.2",
|
||||
"windmill-parser-wasm-csharp": "^1.437.1",
|
||||
"windmill-parser-wasm-go": "^1.429.0",
|
||||
"windmill-parser-wasm-php": "^1.429.0",
|
||||
"windmill-parser-wasm-py": "^1.429.0",
|
||||
|
||||
@@ -191,6 +191,7 @@
|
||||
| 'javascript'
|
||||
| 'rust'
|
||||
| 'yaml'
|
||||
| 'csharp'
|
||||
export let code: string = ''
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined
|
||||
export let formatAction: (() => void) | undefined = undefined
|
||||
|
||||
@@ -91,7 +91,8 @@
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust'
|
||||
'rust',
|
||||
'csharp'
|
||||
].includes(lang ?? '')
|
||||
$: showVarPicker = [
|
||||
'python3',
|
||||
@@ -103,7 +104,8 @@
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust'
|
||||
'rust',
|
||||
'csharp'
|
||||
].includes(lang ?? '')
|
||||
$: showResourcePicker = [
|
||||
'python3',
|
||||
@@ -115,7 +117,8 @@
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust'
|
||||
'rust',
|
||||
'csharp'
|
||||
].includes(lang ?? '')
|
||||
$: showResourceTypePicker =
|
||||
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
|
||||
@@ -283,6 +286,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
function windmillPathToCamelCaseName(path: string): string {
|
||||
const parts = path.split('/')
|
||||
const lastPart = parts[parts.length - 1]
|
||||
|
||||
const words = lastPart.split('_')
|
||||
|
||||
return words
|
||||
.map((word, index) => {
|
||||
if (index === 0) {
|
||||
// Lowercase the first word
|
||||
return word.toLowerCase()
|
||||
} else {
|
||||
// Capitalize the first letter of subsequent words
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
}
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
let historyBrowserDrawerOpen = false
|
||||
</script>
|
||||
|
||||
@@ -342,6 +364,10 @@
|
||||
editor.insertAtCursor(`$Env:${name}`)
|
||||
} else if (lang == 'php') {
|
||||
editor.insertAtCursor(`getenv('${name}');`)
|
||||
} else if (lang == 'rust') {
|
||||
editor.insertAtCursor(`std::env::var("${name}").unwrap();`)
|
||||
} else if (lang == 'csharp') {
|
||||
editor.insertAtCursor(`Environment.GetEnvironmentVariable("${name}");`)
|
||||
}
|
||||
sendUserToast(`${name} inserted at cursor`)
|
||||
}}
|
||||
@@ -398,6 +424,15 @@
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . getenv('WM_TOKEN')));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$var = json_decode(curl_exec($ch));`)
|
||||
} else if (lang == 'csharp') {
|
||||
editor.insertAtCursor(`var baseUrl = Environment.GetEnvironmentVariable("BASE_INTERNAL_URL");
|
||||
var workspace = Environment.GetEnvironmentVariable("WM_WORKSPACE");
|
||||
var uri = $"{baseUrl}/api/w/{workspace}/variables/get_value/${path}";
|
||||
using var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("WM_TOKEN")}");
|
||||
|
||||
string ${windmillPathToCamelCaseName(path)} = await client.GetStringAsync(uri);
|
||||
`)
|
||||
}
|
||||
sendUserToast(`${name} inserted at cursor`)
|
||||
}}
|
||||
@@ -468,6 +503,18 @@ $var = json_decode(curl_exec($ch));`)
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . getenv('WM_TOKEN')));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$res = json_decode(curl_exec($ch));`)
|
||||
} else if (lang == 'csharp') {
|
||||
if (!editor.getCode().includes(`using System.Text.Json.Nodes;`)) {
|
||||
editor.insertAtBeginning(`using System.Text.Json.Nodes;\n`)
|
||||
}
|
||||
editor.insertAtCursor(`var baseUrl = Environment.GetEnvironmentVariable("BASE_INTERNAL_URL");
|
||||
var workspace = Environment.GetEnvironmentVariable("WM_WORKSPACE");
|
||||
var uri = $"{baseUrl}/api/w/{workspace}/resources/get_value_interpolated/${path}";
|
||||
using var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("WM_TOKEN")}");
|
||||
|
||||
JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetStringAsync(uri));
|
||||
`)
|
||||
}
|
||||
sendUserToast(`${path} inserted at cursor`)
|
||||
}}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import powershell from 'svelte-highlight/languages/powershell'
|
||||
import php from 'svelte-highlight/languages/php'
|
||||
import rust from 'svelte-highlight/languages/rust'
|
||||
import csharp from 'svelte-highlight/languages/csharp'
|
||||
import yaml from 'svelte-highlight/languages/yaml'
|
||||
import type { Script } from '$lib/gen'
|
||||
import { Button } from './common'
|
||||
@@ -55,6 +56,8 @@
|
||||
return php
|
||||
case 'rust':
|
||||
return rust
|
||||
case 'csharp':
|
||||
return csharp
|
||||
case 'ansible':
|
||||
return yaml;
|
||||
default:
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { base } from '$lib/base'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
|
||||
export let setting: Setting
|
||||
export let version: string
|
||||
@@ -179,6 +180,14 @@
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if setting.fieldType == 'codearea'}
|
||||
<SimpleEditor
|
||||
autoHeight
|
||||
class="editor"
|
||||
lang={setting.codeAreaLang ?? "txt"}
|
||||
bind:code={$values[setting.key]}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
{:else if setting.fieldType == 'license_key'}
|
||||
{@const { valid, expiration } = parseLicenseKey($values[setting.key] ?? '')}
|
||||
<div class="flex gap-2">
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
'bun',
|
||||
'php',
|
||||
'rust',
|
||||
'ansible'
|
||||
'ansible',
|
||||
'csharp'
|
||||
]
|
||||
const nativeTags = [
|
||||
'nativets',
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import PHPIcon from '$lib/components/icons/PHPIcon.svelte'
|
||||
import RustIcon from '$lib/components/icons/RustIcon.svelte'
|
||||
import AnsibleIcon from '$lib/components/icons/AnsibleIcon.svelte'
|
||||
import CSharpIcon from '$lib/components/icons/CSharpIcon.svelte'
|
||||
|
||||
export let lang:
|
||||
| SupportedLanguage
|
||||
@@ -50,7 +51,8 @@
|
||||
bun: 'TypeScript',
|
||||
php: 'PHP',
|
||||
rust: 'Rust',
|
||||
ansible: 'Ansible Playbook'
|
||||
ansible: 'Ansible Playbook',
|
||||
csharp: 'C sharpo'
|
||||
}
|
||||
|
||||
const langToComponent: Record<
|
||||
@@ -78,7 +80,8 @@
|
||||
graphql: GraphqlIcon,
|
||||
php: PHPIcon,
|
||||
rust: RustIcon,
|
||||
ansible: AnsibleIcon
|
||||
ansible: AnsibleIcon,
|
||||
csharp: CSharpIcon
|
||||
}
|
||||
|
||||
let subIconScale = width === 30 ? 0.6 : 0.8
|
||||
|
||||
14
frontend/src/lib/components/icons/CSharpIcon.svelte
Normal file
14
frontend/src/lib/components/icons/CSharpIcon.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script>
|
||||
export let height = '24px'
|
||||
export let width = '24px'
|
||||
</script>
|
||||
|
||||
<svg {width} {height} viewBox="0 0 256 288" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M255.569,84.452376 C255.567,79.622376 254.534,75.354376 252.445,71.691376 C250.393,68.089376 247.32,65.070376 243.198,62.683376 C209.173,43.064376 175.115,23.505376 141.101,3.86637605 C131.931,-1.42762395 123.04,-1.23462395 113.938,4.13537605 C100.395,12.122376 32.59,50.969376 12.385,62.672376 C4.064,67.489376 0.015,74.861376 0.013,84.443376 C0,123.898376 0.013,163.352376 0,202.808376 C0,207.532376 0.991,211.717376 2.988,215.325376 C5.041,219.036376 8.157,222.138376 12.374,224.579376 C32.58,236.282376 100.394,275.126376 113.934,283.115376 C123.04,288.488376 131.931,288.680376 141.104,283.384376 C175.119,263.744376 209.179,244.186376 243.209,224.567376 C247.426,222.127376 250.542,219.023376 252.595,215.315376 C254.589,211.707376 255.582,207.522376 255.582,202.797376 C255.582,202.797376 255.582,123.908376 255.569,84.452376" fill="#A179DC" fill-rule="nonzero"></path>
|
||||
<path d="M128.182,143.241376 L2.988,215.325376 C5.041,219.036376 8.157,222.138376 12.374,224.579376 C32.58,236.282376 100.394,275.126376 113.934,283.115376 C123.04,288.488376 131.931,288.680376 141.104,283.384376 C175.119,263.744376 209.179,244.186376 243.209,224.567376 C247.426,222.127376 250.542,219.023376 252.595,215.315376 L128.182,143.241376" fill="#280068" fill-rule="nonzero"></path>
|
||||
<path d="M255.569,84.452376 C255.567,79.622376 254.534,75.354376 252.445,71.691376 L128.182,143.241376 L252.595,215.315376 C254.589,211.707376 255.58,207.522376 255.582,202.797376 C255.582,202.797376 255.582,123.908376 255.569,84.452376" fill="#390091" fill-rule="nonzero"></path>
|
||||
<path d="M201.892326,116.294008 L201.892326,129.767692 L215.36601,129.767692 L215.36601,116.294008 L222.102852,116.294008 L222.102852,129.767692 L235.576537,129.767692 L235.576537,136.504534 L222.102852,136.504534 L222.102852,149.978218 L235.576537,149.978218 L235.576537,156.71506 L222.102852,156.71506 L222.102852,170.188744 L215.36601,170.188744 L215.36601,156.71506 L201.892326,156.71506 L201.892326,170.188744 L195.155484,170.188744 L195.155484,156.71506 L181.6818,156.71506 L181.6818,149.978218 L195.155484,149.978218 L195.155484,136.504534 L181.6818,136.504534 L181.6818,129.767692 L195.155484,129.767692 L195.155484,116.294008 L201.892326,116.294008 Z M215.36601,136.504534 L201.892326,136.504534 L201.892326,149.978218 L215.36601,149.978218 L215.36601,136.504534 Z" fill="#FFFFFF"></path>
|
||||
<path d="M128.456752,48.625876 C163.600523,48.625876 194.283885,67.7121741 210.718562,96.0819435 L210.558192,95.808876 L169.209615,119.617159 C161.062959,105.823554 146.128136,96.5150717 128.996383,96.3233722 L128.456752,96.3203544 C102.331178,96.3203544 81.1506705,117.499743 81.1506705,143.625316 C81.1506705,152.168931 83.4284453,160.17752 87.3896469,167.094792 C95.543745,181.330045 110.872554,190.931398 128.456752,190.931398 C146.149522,190.931398 161.565636,181.208041 169.67832,166.820563 L169.481192,167.165876 L210.767678,191.083913 C194.51328,219.21347 164.25027,238.240861 129.514977,238.620102 L128.456752,238.625876 C93.2021701,238.625876 62.4315028,219.422052 46.0382398,190.902296 C38.0352471,176.979327 33.4561922,160.837907 33.4561922,143.625316 C33.4561922,91.1592636 75.9884604,48.625876 128.456752,48.625876 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -13,6 +13,7 @@ export interface Setting {
|
||||
| 'password'
|
||||
| 'select'
|
||||
| 'textarea'
|
||||
| 'codearea'
|
||||
| 'seconds'
|
||||
| 'email'
|
||||
| 'license_key'
|
||||
@@ -33,6 +34,7 @@ export interface Setting {
|
||||
isValid?: (value: any) => boolean
|
||||
error?: string
|
||||
defaultValue?: () => any
|
||||
codeAreaLang?: string,
|
||||
}
|
||||
|
||||
export type SettingStorage = 'setting'
|
||||
@@ -253,6 +255,16 @@ export const settings: Record<string, Setting[]> = {
|
||||
placeholder: '"@myorg3" = { token = "mytoken", url = "https://registry.myorg.com/" }',
|
||||
storage: 'setting',
|
||||
ee_only: ''
|
||||
},
|
||||
{
|
||||
label: 'Nuget Config',
|
||||
description:
|
||||
'Write a nuget.config file to set custom package sources and credentials',
|
||||
key: 'nuget_config',
|
||||
fieldType: 'codearea',
|
||||
codeAreaLang: 'xml',
|
||||
storage: 'setting',
|
||||
ee_only: ''
|
||||
}
|
||||
],
|
||||
Alerts: [
|
||||
|
||||
@@ -80,6 +80,8 @@ export function langToExt(lang: string): string {
|
||||
return 'css'
|
||||
case 'ansible':
|
||||
return 'yml'
|
||||
case 'csharp':
|
||||
return 'cs'
|
||||
default:
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import initGoParser, { parse_go } from 'windmill-parser-wasm-go'
|
||||
import initPhpParser, { parse_php } from 'windmill-parser-wasm-php'
|
||||
import initRustParser, { parse_rust } from 'windmill-parser-wasm-rust'
|
||||
import initYamlParser, { parse_ansible } from 'windmill-parser-wasm-yaml'
|
||||
import initCSharpParser, { parse_csharp } from 'windmill-parser-wasm-csharp'
|
||||
|
||||
import wasmUrlTs from 'windmill-parser-wasm-ts/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlRegex from 'windmill-parser-wasm-regex/windmill_parser_wasm_bg.wasm?url'
|
||||
@@ -29,6 +30,7 @@ import wasmUrlGo from 'windmill-parser-wasm-go/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlPhp from 'windmill-parser-wasm-php/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlRust from 'windmill-parser-wasm-rust/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlYaml from 'windmill-parser-wasm-yaml/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlCSharp from 'windmill-parser-wasm-csharp/windmill_parser_wasm_bg.wasm?url'
|
||||
import { workspaceStore } from './stores.js'
|
||||
import { argSigToJsonSchemaType } from './inferArgSig.js'
|
||||
|
||||
@@ -60,6 +62,9 @@ async function initWasmGo() {
|
||||
async function initWasmYaml() {
|
||||
await initYamlParser(wasmUrlYaml)
|
||||
}
|
||||
async function initWasmCSharp() {
|
||||
await initCSharpParser(wasmUrlCSharp)
|
||||
}
|
||||
|
||||
export async function inferArgs(
|
||||
language: SupportedLanguage | 'bunnative' | undefined,
|
||||
@@ -161,6 +166,9 @@ export async function inferArgs(
|
||||
} else if (language == 'ansible') {
|
||||
await initWasmYaml()
|
||||
inferedSchema = JSON.parse(parse_ansible(code))
|
||||
} else if (language == 'csharp') {
|
||||
await initWasmCSharp()
|
||||
inferedSchema = JSON.parse(parse_csharp(code))
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -348,6 +348,56 @@ fn main(who_to_greet: String, numbers: Vec<i8>) -> anyhow::Result<Ret> {
|
||||
}
|
||||
`
|
||||
|
||||
const CSHARP_INIT_CODE = `
|
||||
#r "nuget: Humanizer, 2.14.1"
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
|
||||
|
||||
class Script
|
||||
{
|
||||
public static int Main(string[] extraWords, string word = "clue", int highNumberThreshold = 50)
|
||||
{
|
||||
Console.WriteLine("Hello, World!");
|
||||
|
||||
Console.WriteLine("Your chosen words are pluralized here:");
|
||||
|
||||
string[] newWordArray = extraWords.Concat(new[] { word }).ToArray();
|
||||
|
||||
foreach (var s in newWordArray)
|
||||
{
|
||||
Console.WriteLine($" {s.Pluralize()}");
|
||||
}
|
||||
|
||||
var random = new Random();
|
||||
int randomNumber = random.Next(1, 101);
|
||||
|
||||
Console.WriteLine($"Random number: {randomNumber}");
|
||||
|
||||
string greeting = randomNumber > highNumberThreshold ? "High number!" : "Low number!";
|
||||
greeting += " (according to the threshold parameter)";
|
||||
Console.WriteLine(greeting);
|
||||
// Humanize a timespan
|
||||
var timespan = TimeSpan.FromMinutes(90);
|
||||
Console.WriteLine($"Timespan: {timespan.Humanize()}");
|
||||
|
||||
// Humanize numbers into words
|
||||
int number = 123;
|
||||
Console.WriteLine($"Number: {number.ToWords()}");
|
||||
|
||||
// Pluralize words
|
||||
string singular = "apple";
|
||||
|
||||
// Humanize date difference
|
||||
var date = DateTime.UtcNow.AddDays(-3);
|
||||
Console.WriteLine($"Date: {date.Humanize()}");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const FETCH_INIT_CODE = `export async function main(
|
||||
url: string | undefined,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET',
|
||||
@@ -772,6 +822,9 @@ export const INITIAL_CODE = {
|
||||
ansible: {
|
||||
script: ANSIBLE_PLAYBOOK_INIT_CODE
|
||||
},
|
||||
csharp: {
|
||||
script: CSHARP_INIT_CODE
|
||||
},
|
||||
docker: {
|
||||
script: DOCKER_INIT_CODE
|
||||
},
|
||||
@@ -876,6 +929,8 @@ export function initialCode(
|
||||
return INITIAL_CODE.rust.script
|
||||
} else if (language == 'ansible') {
|
||||
return INITIAL_CODE.ansible.script
|
||||
} else if (language == 'csharp') {
|
||||
return INITIAL_CODE.csharp.script
|
||||
} else if (language == 'bun' || language == 'bunnative') {
|
||||
if (kind == 'trigger') {
|
||||
return INITIAL_CODE.bun.trigger
|
||||
|
||||
@@ -39,6 +39,8 @@ export function scriptLangToEditorLang(
|
||||
return 'graphql'
|
||||
} else if (lang == 'ansible') {
|
||||
return 'yaml'
|
||||
} else if (lang == 'csharp') {
|
||||
return 'csharp'
|
||||
} else if (lang == undefined) {
|
||||
return 'typescript'
|
||||
} else {
|
||||
@@ -116,6 +118,7 @@ const scriptLanguagesArray: [SupportedLanguage | 'docker' | 'bunnative', string]
|
||||
['php', 'PHP'],
|
||||
['rust', 'Rust'],
|
||||
['ansible', 'Ansible Playbook'],
|
||||
['csharp', 'C#'],
|
||||
['docker', 'Docker']
|
||||
]
|
||||
export function processLangs(selected: string | undefined, langs: string[]): string[] {
|
||||
|
||||
@@ -730,7 +730,7 @@
|
||||
<Badge color="blue">priority: {job.priority}</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'nativets', 'bash', 'powershell', 'php', 'rust', 'other', 'dependency'].includes(job.tag)}
|
||||
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'nativets', 'bash', 'powershell', 'php', 'rust', 'other', 'ansible', 'csharp', 'dependency'].includes(job.tag)}
|
||||
<div>
|
||||
<Badge color="indigo">Tag: {job.tag}</Badge>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user