From 5c32a4ae1b69dcbbed9c75990646704ecbdf2e64 Mon Sep 17 00:00:00 2001 From: pyranota <92104930+pyranota@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:12:55 +0100 Subject: [PATCH] feat: add java support (#5458) * feat: add nu (nushell) support * add worker tests * deactivate tables and non-any types below top-level full support will come in V1 for V0 it's better to keep things minimal and simple * add syntax highlighting used python's grammar, since nushell isn't supported by monaco nor svelte-highlights for V1 nu will get it`s own grammar * add logo * partially implement plugin support * change logo + ability to deploy + nsjail draft * static variables + get_resource + get_variable * lsp/dev.nu + initial nu lsp (not working yet) * make it work with nsjail * nullguard * Much more flexible signature parsing and better error-messages * add init script * rename nulsp to nu * install nu to dockerfile * fix merge * implement Default for MainArgSignature * stage NU_CACHE_DIR * improve dockerfiles * dev.nu for parser-wasm + flake.nix * update code for windows * add nushell to flake * upload Cargo.lock * make build.sh work on nixos * build wasm cli parsers * add docs to README_DEV.md * add helper script docker/dev.nu * improve docker/dev.nu * fix windows * commit frontend/package(lock).json * update cargo.lock * correctly update cargo.lock * remove lsp * update flake.nix to include svelte server and nushell * Revert base.sql to main * remove PLUGIN_USE_RE * make CARGO_PATH private * add nu to cli * Change flags to build wasm-nu-parser * remove flake.nix from parser-wasm * update wasm-build target * remove unused import * add cli support for nu * update github workflows * wasm-build 0.17 -> 0.19 * update build script * update cargo.lock * Fix typographical error * start working on java * do java boilerplate * implement parser for java * update Cargo.lock * update ENV_SETTINGS * use published nu parser * update package.lock * java is S3 + Caching enabled * install nsjail backup * commit v0 * fix nsjail * v0.1 * rewrite parser in tree-sitter * implement parser from scratch * polishing * change init script to match new parser * fix imports * fix cli build * fix cli build * refactor install phase * implement .valid.windmill atomic verification * implement java init functionality * remove quick-xml * fix windows not recognizing 'mvn' * create empty settings.xml if there is no config provided * clean up * change default settings.xml * change classpath format for windows * docs to helper * java copy bin cache instead of symlink * remove comments * merge * fix package.json * fix package.json 2 * minor fixing * migrate to Coursier * update misc * Http(s) Proxy + CA certs * remove unused .wasm * make requirements insensitive to spaces * update handle_child refs * rework save_cache for directories * fix s3 bug * compile .wasm for cli * remove uuid import * fix compilation * use reference * fix zero-dep failure * removing unsafe stuff * remove unneeded imports * revert: we still need winapi * remove nix store from nsjail * do not create cache_nomount * add java to dnt * remove duplicated dependency in init script * fix typos * fix CI * use published parser --- .github/workflows/build-publish-rh-image.yml | 4 +- .github/workflows/build_windows_worker_.yml | 3 +- .github/workflows/docker-image-rpi4.yml | 2 +- .github/workflows/docker-image.yml | 4 +- .github/workflows/publish_windows_worker.yml | 3 +- .gitignore | 1 + backend/Cargo.lock | 27 + backend/Cargo.toml | 26 +- .../20250304181111_add_java.down.sql | 1 + .../migrations/20250304181111_add_java.up.sql | 3 + .../parsers/windmill-parser-java/Cargo.toml | 21 + .../parsers/windmill-parser-java/src/lib.rs | 446 +++++++++ .../windmill-parser-java/src/wasm_libc.rs | 207 ++++ .../parsers/windmill-parser-wasm/Cargo.toml | 2 + .../windmill-parser-wasm/build-pkgs-cli.sh | 6 +- .../windmill-parser-wasm/build-pkgs-mac.sh | 5 + .../windmill-parser-wasm/build-pkgs.sh | 4 + backend/parsers/windmill-parser-wasm/build.sh | 8 - .../parsers/windmill-parser-wasm/src/lib.rs | 8 + backend/src/main.rs | 28 +- backend/src/monitor.rs | 20 +- backend/tests/worker.rs | 45 + backend/windmill-api/openapi.yaml | 4 +- backend/windmill-api/src/scripts.rs | 25 +- backend/windmill-api/src/workspaces_export.rs | 2 + backend/windmill-common/Cargo.toml | 1 + .../windmill-common/src/global_settings.rs | 4 + backend/windmill-common/src/schema.rs | 30 +- backend/windmill-common/src/scripts.rs | 4 + backend/windmill-common/src/worker.rs | 123 ++- backend/windmill-worker/Cargo.toml | 6 +- .../nsjail/run.java.config.proto | 107 ++ .../windmill-worker/src/ansible_executor.rs | 2 + backend/windmill-worker/src/bash_executor.rs | 3 + backend/windmill-worker/src/bun_executor.rs | 20 +- backend/windmill-worker/src/common.rs | 580 ++++++++++- .../windmill-worker/src/csharp_executor.rs | 7 +- backend/windmill-worker/src/deno_executor.rs | 2 + backend/windmill-worker/src/global_cache.rs | 36 +- backend/windmill-worker/src/go_executor.rs | 8 +- backend/windmill-worker/src/handle_child.rs | 13 +- backend/windmill-worker/src/java_executor.rs | 915 ++++++++++++++++++ backend/windmill-worker/src/lib.rs | 8 +- backend/windmill-worker/src/nu_executor.rs | 1 + backend/windmill-worker/src/php_executor.rs | 2 + .../windmill-worker/src/python_executor.rs | 47 +- backend/windmill-worker/src/rust_executor.rs | 7 +- backend/windmill-worker/src/worker.rs | 51 +- .../windmill-worker/src/worker_lockfiles.rs | 26 +- cli/bootstrap/script_bootstrap.ts | 8 + cli/dnt.ts | 1 + cli/gen/services.gen.ts | 2 +- cli/gen/types.gen.ts | 4 +- cli/metadata.ts | 6 +- cli/script.ts | 6 + cli/script_common.ts | 7 +- cli/sync.ts | 4 + cli/types.ts | 2 + cli/wasm/java/windmill_parser_wasm.d.ts | 3 + cli/wasm/java/windmill_parser_wasm.js | 121 +++ cli/wasm/java/windmill_parser_wasm_bg.wasm | Bin 0 -> 499719 bytes .../java/windmill_parser_wasm_bg.wasm.d.ts | 29 + docker/DockerfileFull | 7 + docker/DockerfileFullEe | 7 + docker/DockerfileNsjail | 8 + flake.nix | 43 +- frontend/package-lock.json | 6 + frontend/package.json | 1 + frontend/src/lib/components/Editor.svelte | 2 + frontend/src/lib/components/EditorBar.svelte | 22 +- .../src/lib/components/HighlightCode.svelte | 6 +- .../src/lib/components/ScriptEditor.svelte | 2 +- .../src/lib/components/WorkerGroup.svelte | 4 +- .../common/languageIcons/LanguageIcon.svelte | 9 +- .../src/lib/components/icons/JavaIcon.svelte | 6 + .../src/lib/components/instanceSettings.ts | 19 +- frontend/src/lib/editorUtils.ts | 3 + frontend/src/lib/infer.ts | 9 + frontend/src/lib/script_helpers.ts | 64 +- frontend/src/lib/scripts.ts | 10 +- .../(root)/(logged)/run/[...run]/+page.svelte | 3 +- 81 files changed, 3144 insertions(+), 188 deletions(-) create mode 100644 backend/migrations/20250304181111_add_java.down.sql create mode 100644 backend/migrations/20250304181111_add_java.up.sql create mode 100644 backend/parsers/windmill-parser-java/Cargo.toml create mode 100644 backend/parsers/windmill-parser-java/src/lib.rs create mode 100644 backend/parsers/windmill-parser-java/src/wasm_libc.rs delete mode 100755 backend/parsers/windmill-parser-wasm/build.sh create mode 100644 backend/windmill-worker/nsjail/run.java.config.proto create mode 100644 backend/windmill-worker/src/java_executor.rs create mode 100644 cli/wasm/java/windmill_parser_wasm.d.ts create mode 100644 cli/wasm/java/windmill_parser_wasm.js create mode 100644 cli/wasm/java/windmill_parser_wasm_bg.wasm create mode 100644 cli/wasm/java/windmill_parser_wasm_bg.wasm.d.ts create mode 100644 frontend/src/lib/components/icons/JavaIcon.svelte diff --git a/.github/workflows/build-publish-rh-image.yml b/.github/workflows/build-publish-rh-image.yml index 01a1f4fa97..b509064f29 100644 --- a/.github/workflows/build-publish-rh-image.yml +++ b/.github/workflows/build-publish-rh-image.yml @@ -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,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages 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,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages secrets: | rh_username=${{ secrets.RH_USERNAME }} rh_password=${{ secrets.RH_PASSWORD }} diff --git a/.github/workflows/build_windows_worker_.yml b/.github/workflows/build_windows_worker_.yml index 7cc5a56902..d861034187 100644 --- a/.github/workflows/build_windows_worker_.yml +++ b/.github/workflows/build_windows_worker_.yml @@ -51,8 +51,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,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu - + cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages - name: Rename binary with corresponding architecture run: | Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe" diff --git a/.github/workflows/docker-image-rpi4.yml b/.github/workflows/docker-image-rpi4.yml index 8dad6651f8..ed4fd7810b 100644 --- a/.github/workflows/docker-image-rpi4.yml +++ b/.github/workflows/docker-image-rpi4.yml @@ -67,7 +67,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu + features=embedding,parquet,openidconnect,license,http_trigger,zip,oauth2,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev ${{ steps.meta-public.outputs.tags }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index cf95bcd108..72eaf99004 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -95,7 +95,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu + features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} ${{ steps.meta-public.outputs.tags }} @@ -157,7 +157,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,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} ${{ steps.meta-ee-public.outputs.tags }} diff --git a/.github/workflows/publish_windows_worker.yml b/.github/workflows/publish_windows_worker.yml index 1ef3d32b99..20a30e936f 100644 --- a/.github/workflows/publish_windows_worker.yml +++ b/.github/workflows/publish_windows_worker.yml @@ -53,8 +53,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,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust,nu - + cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages - name: Rename binary with corresponding architecture run: | Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe" diff --git a/.gitignore b/.gitignore index 60c80becbf..b157a1679a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ CaddyfileRemoteMalo .direnv .vscode .dev-docker-wrapper* +backend/.minio-data diff --git a/backend/Cargo.lock b/backend/Cargo.lock index c06a2031ab..d050af109b 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -12792,6 +12792,16 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa6cbcdc8c679b214e616fd3300da67da0e492e066df01bcf5a5921a71e90d6" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "tree-sitter-language" version = "0.1.5" @@ -13878,6 +13888,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sqlx", + "tar", "tempfile", "thiserror 2.0.12", "tikv-jemalloc-ctl", @@ -13998,6 +14009,18 @@ dependencies = [ "windmill-parser", ] +[[package]] +name = "windmill-parser-java" +version = "1.478.1" +dependencies = [ + "anyhow", + "serde_json", + "tree-sitter", + "tree-sitter-java", + "wasm-bindgen", + "windmill-parser", +] + [[package]] name = "windmill-parser-nu" version = "1.478.1" @@ -14112,6 +14135,7 @@ dependencies = [ "windmill-parser-csharp", "windmill-parser-go", "windmill-parser-graphql", + "windmill-parser-java", "windmill-parser-nu", "windmill-parser-php", "windmill-parser-py", @@ -14236,17 +14260,20 @@ dependencies = [ "tokio-postgres 0.7.13", "tokio-util", "tracing", + "url", "urlencoding", "uuid", "winapi", "windmill-audit", "windmill-common", "windmill-git-sync", + "windmill-macros", "windmill-parser", "windmill-parser-bash", "windmill-parser-csharp", "windmill-parser-go", "windmill-parser-graphql", + "windmill-parser-java", "windmill-parser-nu", "windmill-parser-php", "windmill-parser-py", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f45590e562..56ad06234e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -23,6 +23,7 @@ members = [ "./parsers/windmill-parser-rust", "./parsers/windmill-parser-csharp", "./parsers/windmill-parser-nu", + "./parsers/windmill-parser-java", "./parsers/windmill-parser-bash", "./parsers/windmill-parser-py", "./parsers/windmill-parser-py-imports", @@ -62,31 +63,34 @@ cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"] tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "windmill-common/tantivy", "enterprise", "parquet"] sqlx = ["windmill-worker/sqlx"] -deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"] kafka = ["windmill-api/kafka"] nats = ["windmill-api/nats"] otel = ["windmill-common/otel", "windmill-worker/otel"] dind = ["windmill-worker/dind"] -php = ["windmill-worker/php"] -rust = ["windmill-worker/rust"] -mysql = ["windmill-worker/mysql"] -oracledb = ["windmill-worker/oracledb"] -mssql = ["windmill-worker/mssql"] -bigquery = ["windmill-worker/bigquery"] websocket = ["windmill-api/websocket"] postgres_trigger = ["windmill-api/postgres_trigger"] mqtt_trigger = ["windmill-api/mqtt_trigger"] sqs_trigger = ["windmill-api/sqs_trigger"] -python = ["windmill-worker/python"] smtp = ["windmill-api/smtp", "windmill-common/smtp"] -csharp = ["windmill-worker/csharp"] -nu = ["windmill-worker/nu"] license = ["windmill-api/license"] oauth2 = ["windmill-api/oauth2"] http_trigger = ["windmill-api/http_trigger"] zip = ["windmill-api/zip"] static_frontend = ["windmill-api/static_frontend"] scoped_cache = ["windmill-common/scoped_cache"] +# Languages +python = ["windmill-worker/python"] +deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"] +rust = ["windmill-worker/rust"] +mysql = ["windmill-worker/mysql"] +oracledb = ["windmill-worker/oracledb"] +mssql = ["windmill-worker/mssql"] +bigquery = ["windmill-worker/bigquery"] +php = ["windmill-worker/php"] +csharp = ["windmill-worker/csharp"] +nu = ["windmill-worker/nu"] +java = ["windmill-worker/java"] +all_languages = [ "python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"] [dependencies] @@ -158,6 +162,7 @@ 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-java = { path = "./parsers/windmill-parser-java" } windmill-parser-nu = { path = "./parsers/windmill-parser-nu" } windmill-parser-bash = { path = "./parsers/windmill-parser-bash" } windmill-parser-sql = { path = "./parsers/windmill-parser-sql" } @@ -363,5 +368,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" +tree-sitter-java = "0.23.0" oracle = { version = "0.6.3", features = ["chrono"] } rumqttc = { version = "0.24.0", features = ["use-native-tls"]} diff --git a/backend/migrations/20250304181111_add_java.down.sql b/backend/migrations/20250304181111_add_java.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250304181111_add_java.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250304181111_add_java.up.sql b/backend/migrations/20250304181111_add_java.up.sql new file mode 100644 index 0000000000..57256afe18 --- /dev/null +++ b/backend/migrations/20250304181111_add_java.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'java'; +UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["java"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible", "csharp", "nu"]}'::jsonb AND NOT config->'worker_tags' @> '"java"'::jsonb; diff --git a/backend/parsers/windmill-parser-java/Cargo.toml b/backend/parsers/windmill-parser-java/Cargo.toml new file mode 100644 index 0000000000..19809e3120 --- /dev/null +++ b/backend/parsers/windmill-parser-java/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "windmill-parser-java" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[lib] +name = "windmill_parser_java" +path = "./src/lib.rs" + +[dependencies] +windmill-parser.workspace = true +tree-sitter.workspace = true +tree-sitter-java.workspace = true +anyhow.workspace = true +wasm-bindgen.workspace = true +serde_json.workspace = true +# convert_case.workspace = true +# lazy_static.workspace = true +# regex.workspace = true + diff --git a/backend/parsers/windmill-parser-java/src/lib.rs b/backend/parsers/windmill-parser-java/src/lib.rs new file mode 100644 index 0000000000..93aa348cec --- /dev/null +++ b/backend/parsers/windmill-parser-java/src/lib.rs @@ -0,0 +1,446 @@ +#![cfg_attr(target_arch = "wasm32", feature(c_variadic))] + +#[cfg(target_arch = "wasm32")] +pub mod wasm_libc; + +use anyhow::anyhow; +use anyhow::bail; +use serde_json::Value; +use tree_sitter::Node; +use windmill_parser::Arg; +use windmill_parser::MainArgSignature; +use windmill_parser::Typ; + +#[derive(Debug)] +pub struct JavaMainSigMeta { + pub is_public: bool, + pub returns_void: bool, + pub class_name: Option, + pub main_sig: MainArgSignature, +} + +pub fn parse_java_sig_meta(code: &str) -> anyhow::Result { + let mut parser = tree_sitter::Parser::new(); + let language = tree_sitter_java::LANGUAGE; + parser + .set_language(&language.into()) + .map_err(|e| anyhow!("Error setting Java as language: {e}"))?; + + // Parse code + let tree = parser + .parse(code, None) + .ok_or(anyhow!("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_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())? == "public" { + is_public = true; + } + } + if let Some(return_type) = sig.child_by_field_name("type") { + let return_type = return_type.utf8_text(code.as_bytes())?; + + if return_type == "void" { + 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() == "formal_parameter" { + let (otyp, typ, name, default) = parse_java_typ(p_list_node, code)?; + args.push(Arg { + name, + otyp, + typ, + has_default: default.is_some(), + default, + oidx: None, + }); + } + } + } + } + + let main_sig = MainArgSignature { + star_args: false, + star_kwargs: false, + args, + has_preprocessor: None, + no_main_func, + }; + + Ok(JavaMainSigMeta { returns_void, class_name, main_sig, is_public }) +} + +pub fn parse_java_signature(code: &str) -> anyhow::Result { + Ok(parse_java_sig_meta(code)?.main_sig) +} + +fn find_typ<'a>(typ_node: Node<'a>, code: &str) -> anyhow::Result<(Typ, Option)> { + let null = Some(serde_json::Value::Null); + let res = match typ_node.kind() { + #[rustfmt::skip] + "type_identifier" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("String") => (Typ::Str(None), null), + Ok("Byte") => (Typ::Bytes, null), + Ok("Short") => (Typ::Int, null), + Ok("Integer") => (Typ::Int, null), + Ok("Long") => (Typ::Int, null), + Ok("Float") => (Typ::Float, null), + Ok("Double") => (Typ::Float, null), + Ok("Boolean") => (Typ::Bool, null), + Ok("Character") => (Typ::Str(None), null), + Ok("Object") => (Typ::Object(vec![]),null), // TODO: Complete the object type + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + #[rustfmt::skip] + "integral_type" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("byte") => (Typ::Bytes, None), + Ok("short") => (Typ::Int, None), + Ok("int") => (Typ::Int, None), + Ok("long") => (Typ::Int, None), + Ok("char") => (Typ::Str(None), None), + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + "floating_point_type" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("float") => (Typ::Float, None), + Ok("double") => (Typ::Float, None), + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + "boolean_type" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("boolean") => (Typ::Bool, None), + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + "array_type" => { + let new_typ_node = typ_node + .named_child(0) + .ok_or(anyhow!("Failed to find inner type of array type"))?; + (Typ::List(Box::new(find_typ(new_typ_node, code)?.0)), null) + } + wc => bail!( + "Unexpected Java type node kind: {} for '{}'. This type is not handled by Windmill, please open an issue if this seems to be an error", + wc, + typ_node.utf8_text(code.as_bytes())? + ), + + }; + Ok(res) +} + +fn parse_java_typ<'a>( + param_node: Node<'a>, + code: &str, +) -> anyhow::Result<(Option, Typ, String, Option)> { + 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, default) = find_typ( + otyp_node.ok_or(anyhow!( + "Internal error: Failed to get child by field name 'type'" + ))?, + code, + )?; + + Ok((otyp, typ, name.to_string(), default)) +} + +// Function to find the Main method's signature +fn find_main_signature<'a>(root_node: Node<'a>, code: &str) -> Option<(Node<'a>, Option)> { + 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() == "class_body" { + 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; +} + +#[cfg(test)] +mod test { + + use serde_json::json; + + use super::*; + #[test] + fn test_parse_java_return_void() { + let code = r#" +class Main { + public static void main() {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + assert!(sig_meta.returns_void); + } + #[test] + fn test_parse_java_return_object() { + let code = r#" +class Main { + public static Object main() {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + assert!(!sig_meta.returns_void); + } + #[test] + fn test_parse_java_primitive_types() { + let code = r#" +class Main { + public static string main(byte a, short b, int c, long d, float e, double f, boolean g, char h) {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + let ret = sig_meta.main_sig; + assert_eq!( + ret.args, + vec![ + Arg { + name: "a".into(), + otyp: Some("byte".into()), + typ: Typ::Bytes, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "b".into(), + otyp: Some("short".into()), + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "c".into(), + otyp: Some("int".into()), + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "d".into(), + otyp: Some("long".into()), + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "e".into(), + otyp: Some("float".into()), + typ: Typ::Float, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "f".into(), + otyp: Some("double".into()), + typ: Typ::Float, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "g".into(), + otyp: Some("boolean".into()), + typ: Typ::Bool, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "h".into(), + otyp: Some("char".into()), + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None + }, + ] + ); + } + + #[test] + fn test_parse_java_objects() { + let code = r#" +class Main { + public static string main(Byte a, Short b, Integer c, Long d, Float e, Double f, Boolean g, Character h, Object i) {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + let ret = sig_meta.main_sig; + assert_eq!( + ret.args, + vec![ + Arg { + name: "a".into(), + otyp: Some("Byte".into()), + typ: Typ::Bytes, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "b".into(), + otyp: Some("Short".into()), + typ: Typ::Int, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "c".into(), + otyp: Some("Integer".into()), + typ: Typ::Int, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "d".into(), + otyp: Some("Long".into()), + typ: Typ::Int, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "e".into(), + otyp: Some("Float".into()), + typ: Typ::Float, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "f".into(), + otyp: Some("Double".into()), + typ: Typ::Float, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "g".into(), + otyp: Some("Boolean".into()), + typ: Typ::Bool, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "h".into(), + otyp: Some("Character".into()), + typ: Typ::Str(None), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "i".into(), + otyp: Some("Object".into()), + typ: Typ::Object(vec![]), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + ] + ); + } + #[test] + fn test_parse_java_array() { + let code = r#" +class Main { + public static string main(int[] a, Object[] b, String[] c) {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + let ret = sig_meta.main_sig; + assert_eq!( + ret.args, + vec![ + Arg { + name: "a".into(), + otyp: Some("int[]".into()), + typ: Typ::List(Box::new(Typ::Int)), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "b".into(), + otyp: Some("Object[]".into()), + typ: Typ::List(Box::new(Typ::Object(vec![]))), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "c".into(), + otyp: Some("String[]".into()), + typ: Typ::List(Box::new(Typ::Str(None))), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + ] + ); + } +} diff --git a/backend/parsers/windmill-parser-java/src/wasm_libc.rs b/backend/parsers/windmill-parser-java/src/wasm_libc.rs new file mode 100644 index 0000000000..7d260af2e8 --- /dev/null +++ b/backend/parsers/windmill-parser-java/src/wasm_libc.rs @@ -0,0 +1,207 @@ +use std::collections::BTreeMap; +use std::sync::{Mutex, OnceLock}; +use std::{ + alloc::{self, Layout}, + ffi::{c_char, c_int, c_void}, + mem::align_of, + ptr, +}; +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::() + .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::() + .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!("clock_gettime is not supported"); +} + +// 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"); +} diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index fe450c32b4..5b1c884143 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -28,6 +28,7 @@ graphql-parser = [ "dep:windmill-parser-graphql"] ansible-parser = [ "dep:windmill-parser-yaml"] csharp-parser = [ "dep:windmill-parser-csharp"] nu-parser = [ "dep:windmill-parser-nu"] +java-parser = [ "dep:windmill-parser-java"] [dependencies] anyhow.workspace = true @@ -43,6 +44,7 @@ windmill-parser-rust = { workspace = true, optional = true } windmill-parser-yaml = { workspace = true, optional = true } windmill-parser-csharp = { workspace = true, optional = true } windmill-parser-nu = { workspace = true, optional = true } +windmill-parser-java = { workspace = true, optional = true } wasm-bindgen.workspace = true serde_json.workspace = true getrandom = { workspace = true, features = ["js"] } diff --git a/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh b/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh index 34c36e8820..33c6701e52 100755 --- a/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh +++ b/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh @@ -70,6 +70,10 @@ OUT_DIR="../../../cli/wasm/nu" mkdir -p $OUT_DIR wasm-pack build --release --target deno --out-dir $OUT_DIR --features "nu-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort -# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-nu"/' $OUT_DIR/package.json rm $OUT_DIR/.gitignore +#-# Java +OUT_DIR="../../../cli/wasm/java" +mkdir -p $OUT_DIR +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 deno --out-dir $OUT_DIR --features "java-parser" +rm $OUT_DIR/.gitignore diff --git a/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh b/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh index 85de323a90..109a48e9b3 100755 --- a/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh +++ b/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh @@ -61,3 +61,8 @@ OUT_DIR="pkg-nu" wasm-pack build --release --target web --out-dir $OUT_DIR --features "nu-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-nu"/' $OUT_DIR/package.json + +#-# Java (needs some more stuff to compile C tree sitter into wasm) +OUT_DIR="pkg-java" +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 "java-parser" +sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-java"/' $OUT_DIR/package.json diff --git a/backend/parsers/windmill-parser-wasm/build-pkgs.sh b/backend/parsers/windmill-parser-wasm/build-pkgs.sh index e2f0b6b471..0d492e9dcb 100755 --- a/backend/parsers/windmill-parser-wasm/build-pkgs.sh +++ b/backend/parsers/windmill-parser-wasm/build-pkgs.sh @@ -60,3 +60,7 @@ wasm-pack build --release --target web --out-dir $OUT_DIR --features "nu-parser" -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-nu"/' $OUT_DIR/package.json +#-# Java (needs some more stuff to compile C tree sitter into wasm) +OUT_DIR="pkg-java" +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 "java-parser" +sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-java"/' $OUT_DIR/package.json diff --git a/backend/parsers/windmill-parser-wasm/build.sh b/backend/parsers/windmill-parser-wasm/build.sh deleted file mode 100755 index c843d2b445..0000000000 --- a/backend/parsers/windmill-parser-wasm/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -eou pipefail - -# Flags are needed to build correctly with C# -# NOTE: Currently does not work with nix nor nixos -CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" deno task wasmbuild --inline --out ../../../cli/wasm/ -p windmill-parser-wasm --all-features - - diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index 2ca8f2f2b0..4dbe849e34 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -155,3 +155,11 @@ pub fn parse_csharp(code: &str) -> String { pub fn parse_nu(code: &str) -> String { wrap_sig(windmill_parser_nu::parse_nu_signature(code)) } + +#[cfg(feature = "java-parser")] +#[wasm_bindgen] +pub fn parse_java(code: &str) -> String { + wrap_sig(windmill_parser_java::parse_java_signature(code)) +} + +// KJQXZ diff --git a/backend/src/main.rs b/backend/src/main.rs index a3ec60f846..aec045c8f3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -9,7 +9,8 @@ use anyhow::Context; use monitor::{ load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config, - reload_instance_python_version_setting, reload_nuget_config_setting, + reload_instance_python_version_setting, reload_maven_repos_setting, + reload_no_default_maven_setting, reload_nuget_config_setting, reload_timeout_wait_result_setting, send_current_log_file_to_object_store, send_logs_to_object_store, }; @@ -37,11 +38,12 @@ use windmill_common::{ EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_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, - TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING, + LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, + NO_DEFAULT_MAVEN_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, TEAMS_SETTING, + TIMEOUT_WAIT_RESULT_SETTING, }, scripts::ScriptLang, stats_ee::schedule_stats, @@ -66,9 +68,9 @@ 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, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, - NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, - PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_PY310_CACHE_DIR, TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, - TAR_PY313_CACHE_DIR, UV_CACHE_DIR, + JAVA_CACHE_DIR, NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, + PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, TAR_PY310_CACHE_DIR, + TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR, }; use crate::monitor::{ @@ -796,6 +798,12 @@ Windmill Community Edition {GIT_VERSION} NUGET_CONFIG_SETTING => { reload_nuget_config_setting(&db).await }, + MAVEN_REPOS_SETTING => { + reload_maven_repos_setting(&db).await + }, + NO_DEFAULT_MAVEN_SETTING => { + reload_no_default_maven_setting(&db).await + }, KEEP_JOB_DIR_SETTING => { load_keep_job_dir(&db).await; }, @@ -1103,6 +1111,8 @@ pub async fn run_workers( NU_CACHE_DIR, HUB_CACHE_DIR, POWERSHELL_CACHE_DIR, + JAVA_CACHE_DIR, + TAR_JAVA_CACHE_DIR, // KJQXZ ] { DirBuilder::new() .recursive(true) diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index c94651345d..1209b2aca3 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -52,9 +52,7 @@ use windmill_common::{ }; use windmill_queue::{cancel_job, MiniPulledJob}; use windmill_worker::{ - create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender, - SendResult, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, - NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY, + create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender, SendResult, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY }; #[cfg(feature = "parquet")] @@ -186,6 +184,8 @@ pub async fn initial_load( reload_bunfig_install_scopes_setting(&db).await; reload_instance_python_version_setting(&db).await; reload_nuget_config_setting(&db).await; + reload_maven_repos_setting(&db).await; + reload_no_default_maven_setting(&db).await; } } @@ -969,6 +969,20 @@ pub async fn reload_nuget_config_setting(db: &DB) { ) .await; } +pub async fn reload_maven_repos_setting(db: &DB) { + reload_option_setting_with_tracing(db, windmill_common::global_settings::MAVEN_REPOS_SETTING, "MAVEN_REPOS", MAVEN_REPOS.clone()) + .await; +} +pub async fn reload_no_default_maven_setting(db: &DB) { + let value = load_value_from_global_settings(db, windmill_common::global_settings::NO_DEFAULT_MAVEN_SETTING).await; + match value { + Ok(Some(serde_json::Value::Bool(t))) => NO_DEFAULT_MAVEN.store(t, Ordering::Relaxed), + Err(e) => { + tracing::error!("Error loading no default maven repository: {e:#}"); + } + _ => (), + }; +} pub async fn reload_retention_period_setting(db: &DB) { if let Err(e) = reload_setting( diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 2c1e47f2af..f942a8ee6e 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -1988,6 +1988,51 @@ def main [ assert_eq!(result, serde_json::json!(0)); } +#[cfg(feature = "java")] +#[sqlx::test(fixtures("base"))] +async fn test_java_job(db: Pool) { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await; + let port = server.addr.port(); + + let content = r#" +public class Main { + public static Object main( + // Primitive + int a, + float b, + // Objects + Integer age, + Float d + ){ + return "hello world"; + } +} + +"# + .to_owned(); + + let job = RunJob::from(JobPayload::Code(RawCode { + hash: None, + content, + path: None, + lock: None, + language: ScriptLang::Java, + custom_concurrency_key: None, + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + })) + .arg("a", json!(3)) + .arg("b", json!(3.0)) + .arg("age", json!(30)) + .arg("d", json!(3.0)) + .run_until_complete(&db, port) + .await; + assert_eq!(job.json_result(), Some(json!("hello world"))); +} + #[sqlx::test(fixtures("base"))] async fn test_python_job(db: Pool) { initialize_tracing().await; diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index acd899e985..c89d759c41 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -13529,7 +13529,9 @@ components: rust, ansible, csharp, - nu + nu, + java + # KJQXZ ] Preview: diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index f370bf33fe..4861490ad0 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -624,17 +624,20 @@ async fn create_script_internal<'c>( .unwrap_or(json!({})); let lock = if ns.codebase.is_some() { Some(String::new()) - } else if !(ns.language == ScriptLang::Python3 - || ns.language == ScriptLang::Go - || ns.language == ScriptLang::Bun - || ns.language == ScriptLang::Bunnative - || ns.language == ScriptLang::Deno - || ns.language == ScriptLang::Rust - || ns.language == ScriptLang::Ansible - || ns.language == ScriptLang::CSharp - || ns.language == ScriptLang::Nu - || ns.language == ScriptLang::Php) - { + } else if !( + ns.language == ScriptLang::Python3 + || ns.language == ScriptLang::Go + || ns.language == ScriptLang::Bun + || ns.language == ScriptLang::Bunnative + || ns.language == ScriptLang::Deno + || ns.language == ScriptLang::Rust + || ns.language == ScriptLang::Ansible + || ns.language == ScriptLang::CSharp + || ns.language == ScriptLang::Nu + || ns.language == ScriptLang::Php + || ns.language == ScriptLang::Java + // KJQXZ + ) { Some(String::new()) } else { ns.lock diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 1e4a689b18..ef5e089bd3 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -375,6 +375,8 @@ pub(crate) async fn tarball_workspace( ScriptLang::CSharp => "cs", ScriptLang::Nu => "nu", ScriptLang::OracleDB => "odb.sql", + ScriptLang::Java => "java", + // KJQXZ }; archive .write_to_archive(&script.content, &format!("{}.{}", script.path, ext)) diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index 7ff360b42b..fd370aedcc 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -24,6 +24,7 @@ name = "windmill_common" path = "src/lib.rs" [dependencies] +tar.workspace = true hmac.workspace = true sha2.workspace = true thiserror.workspace = true diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index e0c091a0da..f92eac988c 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -11,6 +11,8 @@ 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 MAVEN_REPOS_SETTING: &str = "maven_repos"; +pub const NO_DEFAULT_MAVEN_SETTING: &str = "no_default_maven"; pub const EXTRA_PIP_INDEX_URL_SETTING: &str = "pip_extra_index_url"; pub const PIP_INDEX_URL_SETTING: &str = "pip_index_url"; @@ -61,6 +63,8 @@ pub const ENV_SETTINGS: &[&str] = &[ "NU_PATH", "DENO_PATH", "GO_PATH", + "JAVA_PATH", + // KJQXZ "GOPRIVATE", "GOPROXY", "NETRC", diff --git a/backend/windmill-common/src/schema.rs b/backend/windmill-common/src/schema.rs index 4b97f761ae..201cdbe92f 100644 --- a/backend/windmill-common/src/schema.rs +++ b/backend/windmill-common/src/schema.rs @@ -345,27 +345,15 @@ fn find_annotation(comm_lit: &str, annotation: &str, code: &str) -> bool { pub fn should_validate_schema(code: &str, lang: &ScriptLang) -> bool { let annotation = "schema_validation"; - match lang { - ScriptLang::Nativets | ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno => { - find_annotation("//", annotation, code) - } - ScriptLang::Python3 => find_annotation("#", annotation, code), - ScriptLang::Go => find_annotation("#", annotation, code), - ScriptLang::Bash => find_annotation("#", annotation, code), - ScriptLang::Powershell => find_annotation("#", annotation, code), - ScriptLang::Postgresql => find_annotation("--", annotation, code), - ScriptLang::Mysql => find_annotation("--", annotation, code), - ScriptLang::Bigquery => find_annotation("--", annotation, code), - ScriptLang::Snowflake => find_annotation("--", annotation, code), - ScriptLang::Graphql => find_annotation("#", annotation, code), - ScriptLang::Mssql => find_annotation("--", annotation, code), - ScriptLang::OracleDB => find_annotation("--", annotation, code), - ScriptLang::Php => find_annotation("//", annotation, code), - ScriptLang::Rust => find_annotation("//!", annotation, code), - ScriptLang::Ansible => find_annotation("#", annotation, code), - ScriptLang::CSharp => find_annotation("//", annotation, code), - ScriptLang::Nu => find_annotation("#", annotation, code), - } + use ScriptLang::*; + let comment = match lang { + Nativets | Bun | Bunnative | Deno | Php | CSharp | Java => "//", + Python3 | Go | Bash | Powershell | Graphql | Ansible | Nu => "#", + Postgresql | Mysql | Bigquery | Snowflake | Mssql | OracleDB => "--", + Rust => "//!", + // KJQXZ + }; + find_annotation(comment, annotation, code) } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index b38598f0b8..ab478b40e0 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -51,6 +51,8 @@ pub enum ScriptLang { Ansible, CSharp, Nu, + Java, + // KJQXZ } impl ScriptLang { @@ -76,6 +78,8 @@ impl ScriptLang { ScriptLang::Ansible => "ansible", ScriptLang::CSharp => "csharp", ScriptLang::Nu => "nu", + ScriptLang::Java => "java", + // KJQXZ } } } diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index a6979ad30e..39cfc7771a 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -9,7 +9,7 @@ use serde_json::value::RawValue; use std::{ cmp::Reverse, collections::{HashMap, HashSet}, - fs::File, + fs::{self, File}, io::Write, path::{Component, Path, PathBuf}, str::FromStr, @@ -51,6 +51,8 @@ lazy_static::lazy_static! { "ansible".to_string(), "csharp".to_string(), "nu".to_string(), + "java".to_string(), + // KJQXZ "dependency".to_string(), "flow".to_string(), "other".to_string() @@ -402,8 +404,43 @@ pub struct SqlAnnotations { pub struct BashAnnotations { pub docker: bool, } +/// length = 5 +/// value = "foo" +/// output = "foo " +/// 12345 +pub fn pad_string(value: &str, total_length: usize) -> String { + if value.len() >= total_length { + value.to_string() // Return the original string if it's already long enough + } else { + let padding_needed = total_length - value.len(); + format!("{value}{}", " ".repeat(padding_needed)) // Pad with spaces + } +} +pub fn copy_dir_recursively(src: &Path, dst: &Path) -> error::Result<()> { + if !dst.exists() { + fs::create_dir_all(dst)?; + } -pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) { + tracing::debug!("Copying recursively from {:?} to {:?}", src, dst); + + for entry in fs::read_dir(src)? { + let entry = entry?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + + if src_path.is_dir() && !src_path.is_symlink() { + copy_dir_recursively(&src_path, &dst_path)?; + } else { + fs::copy(&src_path, &dst_path)?; + } + } + + tracing::debug!("Finished copying recursively from {:?} to {:?}", src, dst); + + Ok(()) +} + +pub async fn load_cache(bin_path: &str, _remote_path: &str, is_dir: bool) -> (bool, String) { if tokio::fs::metadata(&bin_path).await.is_ok() { (true, format!("loaded from local cache: {}\n", bin_path)) } else { @@ -417,12 +454,22 @@ pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) { use crate::s3_helpers::attempt_fetch_bytes; if let Ok(mut x) = attempt_fetch_bytes(os, _remote_path).await { - if let Err(e) = write_binary_file(bin_path, &mut x) { - tracing::error!("could not write bundle/bin file locally: {e:?}"); - return ( - false, - "error writing bundle/bin file from object store".to_string(), - ); + if is_dir { + if let Err(e) = extract_tar(x, bin_path).await { + tracing::error!("could not write tar archive locally: {e:?}"); + return ( + false, + "error writing tar archive from object store".to_string(), + ); + } + } else { + if let Err(e) = write_binary_file(bin_path, &mut x) { + tracing::error!("could not write bundle/bin file locally: {e:?}"); + return ( + false, + "error writing bundle/bin file from object store".to_string(), + ); + } } tracing::info!("loaded from object store {}", bin_path); return ( @@ -435,6 +482,7 @@ pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) { ); } } + let _ = is_dir; (false, "".to_string()) } } @@ -462,6 +510,7 @@ pub async fn save_cache( local_cache_path: &str, _remote_cache_path: &str, origin: &str, + is_dir: bool, ) -> crate::error::Result { let mut _cached_to_s3 = false; #[cfg(all(feature = "enterprise", feature = "parquet"))] @@ -471,11 +520,33 @@ pub async fn save_cache( .clone() { use object_store::path::Path; + let file_to_cache = if is_dir { + let tar_path = format!( + "{ROOT_CACHE_DIR}/tar/{}_tar.tar", + local_cache_path + .split("/") + .last() + .unwrap_or(&uuid::Uuid::new_v4().to_string()) + ); + let tar_file = std::fs::File::create(&tar_path)?; + let mut tar = tar::Builder::new(tar_file); + tar.append_dir_all(".", &origin)?; + let tar_metadata = tokio::fs::metadata(&tar_path).await; + if tar_metadata.is_err() || tar_metadata.as_ref().unwrap().len() == 0 { + tracing::info!("Failed to tar cache: {origin}"); + return Err(error::Error::ExecutionErr(format!( + "Failed to tar cache: {origin}" + ))); + } + tar_path + } else { + origin.to_owned() + }; if let Err(e) = os .put( &Path::from(_remote_cache_path), - std::fs::read(origin)?.into(), + std::fs::read(&file_to_cache)?.into(), ) .await { @@ -485,12 +556,19 @@ pub async fn save_cache( ); } else { _cached_to_s3 = true; + if is_dir { + tokio::fs::remove_dir_all(&file_to_cache).await?; + } } } // if !*CLOUD_HOSTED { if true { - std::fs::copy(origin, local_cache_path)?; + if is_dir { + copy_dir_recursively(&PathBuf::from(origin), &PathBuf::from(local_cache_path))?; + } else { + std::fs::copy(origin, local_cache_path)?; + } Ok(format!( "\nwrote cached binary: {} (backed by EE distributed object store: {_cached_to_s3})\n", local_cache_path @@ -505,6 +583,31 @@ pub async fn save_cache( } } +#[cfg(all(feature = "enterprise", feature = "parquet"))] +pub async fn extract_tar(tar: bytes::Bytes, folder: &str) -> error::Result<()> { + use std::time::Instant; + + use bytes::Buf; + use tokio::fs::{self}; + + let start: Instant = Instant::now(); + fs::create_dir_all(&folder).await?; + + let mut ar = tar::Archive::new(tar.reader()); + + if let Err(e) = ar.unpack(folder) { + tracing::info!("Failed to untar to {folder}. Error: {:?}", e); + fs::remove_dir_all(&folder).await?; + return Err(error::Error::ExecutionErr(format!( + "Failed to untar tar {folder}" + ))); + } + tracing::info!( + "Finished extracting tar to {folder}. Took {}ms", + start.elapsed().as_millis(), + ); + Ok(()) +} #[cfg(all(feature = "enterprise", feature = "parquet"))] fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Result<()> { use std::fs::{File, Permissions}; diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index 291e92c2df..510a33a645 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -30,17 +30,20 @@ python = ["dep:windmill-parser-py", "dep:windmill-parser-py-imports"] csharp = ["dep:windmill-parser-csharp"] rust = ["dep:windmill-parser-rust"] nu = ["dep:windmill-parser-nu"] +java = ["dep:windmill-parser-java"] [dependencies] windmill-queue.workspace = true windmill-audit.workspace = true # there isn't really a reason for audit-worth actions to happen in the worker. windmill-common = { workspace = true, default-features = false } +windmill-macros.workspace = true windmill-parser.workspace = true windmill-parser-ts.workspace = true windmill-parser-go.workspace = true windmill-parser-rust = { workspace = true, optional = true } windmill-parser-csharp = { workspace = true, optional = true } windmill-parser-nu = { workspace = true, optional = true } +windmill-parser-java = { workspace = true, optional = true } windmill-parser-py = { workspace = true, optional = true } windmill-parser-yaml.workspace = true windmill-parser-py-imports = { workspace = true, optional = true } @@ -72,6 +75,7 @@ dyn-iter.workspace = true once_cell.workspace = true tokio-postgres.workspace = true bit-vec.workspace = true +url.workspace = true deno_telemetry = { workspace = true, optional = true } deno_fetch = { workspace = true, optional = true } deno_webidl = { workspace = true, optional = true } @@ -127,4 +131,4 @@ deno_permissions = { workspace = true, optional = true } deno_io = { workspace = true, optional = true } deno_runtime = { workspace = true, optional = true } deno_telemetry = { workspace = true, optional = true } -winapi = { workspace = true, optional = true } \ No newline at end of file +winapi = { workspace = true, optional = true } diff --git a/backend/windmill-worker/nsjail/run.java.config.proto b/backend/windmill-worker/nsjail/run.java.config.proto new file mode 100644 index 0000000000..032072fb7c --- /dev/null +++ b/backend/windmill-worker/nsjail/run.java.config.proto @@ -0,0 +1,107 @@ +name: "java run script" + +mode: ONCE +hostname: "java" +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: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + options: "size=500000000" +} + + +mount { + src: "{JOB_DIR}/target" + dst: "/tmp/target" + 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: "{CACHE_DIR}" + dst: "{CACHE_DIR}" + is_bind: true + mandatory: false +} + +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 + +{SHARED_MOUNT} diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index c98837ac5d..5699c44932 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -170,6 +170,7 @@ async fn install_galaxy_collections( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -432,6 +433,7 @@ fi job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_and_check_result(job_dir).await diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index a496b327b5..85c20fd6fd 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -226,6 +226,7 @@ exit $exit_status job.timeout, true, &mut Some(occupancy_metrics), + None, ) .await?; @@ -569,6 +570,7 @@ pub async fn handle_powershell_job( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; } @@ -780,6 +782,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index ed6881b4fd..382e07eae4 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -166,6 +166,7 @@ pub async fn gen_bun_lockfile( None, false, occupancy_metrics, + None, ) .await?; } else { @@ -349,6 +350,7 @@ pub async fn install_bun_lockfile( None, false, occupancy_metrics, + None, ) .await? } else { @@ -520,6 +522,7 @@ pub async fn generate_wrapper_mjs( timeout, false, occupancy_metrics, + None, ) .await?; fs::rename( @@ -570,6 +573,7 @@ pub async fn generate_bun_bundle( timeout, false, occupancy_metrics, + None, ) .await?; } else { @@ -594,7 +598,6 @@ pub async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<()> { if std::fs::metadata(&bun_cache_path).is_ok() { tracing::info!("loading {bun_cache_path} from cache"); - extract_saved_codebase(job_dir, &bun_cache_path, is_tar, &dst, false)?; } else { #[cfg(all(feature = "enterprise", feature = "parquet"))] @@ -733,7 +736,7 @@ pub async fn prebundle_bun_script( ) .await?; - save_cache(&local_path, &remote_path, &origin).await?; + save_cache(&local_path, &remote_path, &origin, false).await?; Ok(()) } @@ -852,7 +855,8 @@ pub async fn handle_bun_job( ) .await; - let (cache, logs) = windmill_common::worker::load_cache(&local_path, &remote_path).await; + let (cache, logs) = + windmill_common::worker::load_cache(&local_path, &remote_path, false).await; (cache, logs, local_path, remote_path) } else { (false, "".to_string(), "".to_string(), "".to_string()) @@ -1181,7 +1185,14 @@ try {{ ) .await?; if !local_path.is_empty() { - match save_cache(&local_path, &remote_path, &format!("{job_dir}/main.js")).await { + match save_cache( + &local_path, + &remote_path, + &format!("{job_dir}/main.js"), + false, + ) + .await + { Err(e) => { let em = format!("could not save {local_path} to bundle cache: {e:?}"); tracing::error!(em) @@ -1421,6 +1432,7 @@ try {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index cd4f26b776..039ff4205b 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -11,6 +11,7 @@ use sha2::Digest; use sqlx::types::Json; use sqlx::{Pool, Postgres}; use tokio::process::Command; +use tokio::sync::{RwLock, Semaphore}; use tokio::{fs::File, io::AsyncReadExt}; #[cfg(feature = "parquet")] @@ -28,7 +29,7 @@ use windmill_common::{ variables::ContextualVariable, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use windmill_queue::MiniPulledJob; use std::path::Path; @@ -39,7 +40,10 @@ use windmill_common::{variables, DB}; use tokio::{io::AsyncWriteExt, process::Child, time::Instant}; -use crate::{AuthedClient, JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE, MAX_TIMEOUT_DURATION, PATH_ENV}; +use crate::{ + AuthedClient, DISABLE_NSJAIL, JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE, MAX_TIMEOUT_DURATION, + PATH_ENV, +}; pub async fn build_args_map<'a>( job: &'a MiniPulledJob, @@ -946,3 +950,575 @@ pub fn build_http_client(timeout_duration: std::time::Duration) -> error::Result .build() .map_err(|e| Error::internal_err(format!("Error building http client: {e:#}"))) } + +#[derive(Clone)] +pub struct RequiredDependency { + /// Expected directory of dependency in cache + /// For example: + /// /tmp/windmill/cache/python_311/rich==0.0.0 + /// IMPORTANT!: path should not end with '/' + pub path: String, + /// Name to use for S3 tars + /// If not specified will use top level directory of path. + pub custom_name: Option, + /// Display name + /// Name that will be used for console output and logging + /// If not specified will either use custom_name or top level directory of path. + pub short_name: Option, +} + +pub enum InstallStrategy { + /// Will invoke callback to install single dependency + Single(Arc Result + Send + Sync>), + /// Will try to pull S3 first and will invoke closure to install the rest + AllAtOnce(Arc) -> Result + Send + Sync>), +} +/// # General +/// +/// Languages that compile usually include dependencies in final executable. +/// When dynamic languages do not and runtime dependencies provided separately. +/// +/// This helper implies that the language is dynamic. +/// Python, Ruby, Java are dynamic and they can use this helper. +/// +/// # Features +/// +/// This helper will install all specified dependencies in parallel and if it is EE, cache to S3 +/// It has atomic success file, allowing to distinguish failed installations from succesfull. +/// +/// Besides that it provides console output and does logging. +/// +/// # Usage +/// +/// Most important arguments in this helper are `deps` and `install_fn` +/// +/// In `deps` you specify all dependencies that are needed to be on worker in order to execute script. +/// You don't know which are actually installed and which are not. +/// +/// `deps` is a vector of RequiredDependency. Check [RequiredDependency] for more context. +/// +/// After `deps` are provided helper will check each dependency and check if it is in cache, if not it will try to pull from S3 +/// and if it does not work either, it will invoke `install_fn` closure. +/// Closure arguments has dependency name as well as it`s expected path in cache. +/// Closure should return Command that will install dependency to asked place. +pub async fn par_install_language_dependencies<'a>( + deps: Vec, + language_name: &'a str, + installer_executable_name: &'a str, + platform_agnostic: bool, + concurrent_downloads: usize, + stdout_on_err: bool, + install_fn: InstallStrategy, + postinstall_cb: impl AsyncFn(Vec) -> Result<(), error::Error>, + job_id: &'a Uuid, + w_id: &'a str, + worker_name: &'a str, + db: &sqlx::Pool, +) -> anyhow::Result<()> { + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + let _ = (platform_agnostic, language_name); + + let total_time = std::time::Instant::now(); + + // Total to install + let mut not_installed = vec![]; + let total_to_install; + // let mut not_installed = vec![]; + let counter_arc = Arc::new(tokio::sync::Mutex::new(0)); + // Append logs with line like this: + // [9/21] + requests==2.32.3 << (S3) | in 57ms + #[allow(unused_assignments)] + async fn print_success( + mut s3_pull: bool, + mut s3_push: bool, + job_id: &Uuid, + w_id: &str, + req: &str, + req_tl: usize, + counter_arc: Arc>, + total_to_install: usize, + instant: std::time::Instant, + db: Pool, + ) { + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + { + (s3_pull, s3_push) = (false, false); + } + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS + .read() + .await + .is_none() + { + (s3_pull, s3_push) = (false, false); + } + + let mut counter = counter_arc.lock().await; + *counter += 1; + + windmill_queue::append_logs( + job_id, + w_id, + format!( + "\n{}+ {}{}{}| in {}ms", + windmill_common::worker::pad_string( + &format!("[{}/{total_to_install}]", counter), + 9 + ), + // Because we want to align to max len [999/999] we take 9 + // 123456789 + windmill_common::worker::pad_string(&req, req_tl + 1), + // Margin to the right ^ + if s3_pull { "<< (S3) " } else { "" }, + if s3_push { " > (S3) " } else { "" }, + instant.elapsed().as_millis(), + ), + db, + ) + .await; + // Drop lock, so next print success can fire + } + + let mut name_tl = 0; + struct NotInstalledDependency { + path: String, + custom_name: Option, + short_name: Option, + display_name: String, + } + { + let mut to_be_installed_is_used = false; + for RequiredDependency { + path, // + custom_name, + short_name, + } in deps.into_iter() + { + if path.ends_with("/") { + anyhow::bail!("Internal error: path should not end with '/'") + } + let display_name = short_name + .as_ref() + .or(custom_name.as_ref()) + .or(path.split("/").last().map(|e| e.to_owned()).as_ref()) + .unwrap_or_else(|| { + tracing::warn!( + workspace_id = %w_id, + job_id = %job_id, + "failed to parse top level directory name for {path}, fallback to full path.", + ); + + &path + }) + .to_owned(); + { + // Later will help us align text in log console + if display_name.len() > name_tl { + name_tl = display_name.len(); + } + } + // Will look like: /tmp/windmill/cache/lang/dependency.valid.windmill + if tokio::fs::metadata(path.clone() + ".valid.windmill") + .await + .is_err() + { + if !to_be_installed_is_used { + windmill_queue::append_logs( + job_id, + w_id, + format!("\n--- INSTALLATION ---\n\nTo be installed:\n\n"), + db.clone(), + ) + .await; + to_be_installed_is_used = true; + } + windmill_queue::append_logs( + job_id, + w_id, + format!("- {display_name}\n"), + db.clone(), + ) + .await; + not_installed.push(NotInstalledDependency { + path, + custom_name, + short_name, + display_name, + }); + } + } + } + total_to_install = not_installed.len(); + if total_to_install == 0 { + return Ok(()); + } + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let is_not_pro = !matches!( + windmill_common::ee::get_license_plan().await, + windmill_common::ee::LicensePlan::Pro + ); + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if is_not_pro && matches!(install_fn, InstallStrategy::AllAtOnce(_)) { + windmill_queue::append_logs( + job_id, + w_id, + format!("\nLooking for packages on S3:\n"), + db.clone(), + ) + .await; + } + + // Parallelism level (N) + let parallel_limit = // Semaphore will panic if value less then 1 + concurrent_downloads.clamp(1, 30); + + tracing::info!( + workspace_id = %w_id, + "Install parallel limit: {}, job: {}", + parallel_limit, + job_id + ); + + let mut handles = vec![]; + let semaphore = Arc::new(Semaphore::new(parallel_limit)); + let not_pulled = Arc::new(RwLock::new(vec![])); + // let mut handles = Vec::with_capacity(total_to_install); + for NotInstalledDependency { + // + path, + custom_name, + short_name, + display_name, + } in not_installed + { + let permit = semaphore.clone().acquire_owned().await; // Acquire a permit + + if let Err(_) = permit { + tracing::error!( + workspace_id = %w_id, + "Cannot acquire permit on semaphore, that can only mean that semaphore has been closed." + ); + break; + } + + let permit = permit.unwrap(); + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let s3_pull_future = if is_not_pro { + if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS + .read() + .await + .clone() + { + Some(crate::global_cache::pull_from_tar( + os, + path.clone(), + language_name.to_owned(), + custom_name.clone(), + platform_agnostic, + )) + } else { + None + } + } else { + None + }; + let child = { + if let InstallStrategy::Single(ref callback, ..) = install_fn { + let cmd = callback(RequiredDependency { + path: path.clone(), + custom_name: custom_name.clone(), + short_name: short_name.clone(), + })?; + tracing::debug!("{:?}", &cmd); + Some(start_child_process(cmd, &installer_executable_name).await?) + } else { + None + } + }; + + let ( + worker_name_2, + path_2, + display_name_2, + custom_name, + job_id_2, + w_id_2, + db_2, + counter_arc, + language_name, + installer_executable_name, + not_pulled, + ) = ( + worker_name.to_owned(), + path.clone(), + display_name.clone(), + custom_name.clone(), + job_id.clone(), + w_id.to_owned(), + db.clone(), + counter_arc.clone(), + language_name.to_owned(), + installer_executable_name.to_owned(), + not_pulled.clone(), + ); + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + let _ = language_name; + + let start = std::time::Instant::now(); + let handle = tokio::spawn(async move { + let _permit = permit; + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if let Some(s3_pull_future) = s3_pull_future { + if let Err(e) = s3_pull_future.await { + tracing::info!( + workspace_id = %w_id_2, + "No tarball was found for {:?} on S3 or different problem occured {job_id_2}:\n{e}", + &custom_name.clone().unwrap_or(path) + ); + } else { + // TODO: Refactor + // Create a file to indicate that installation was successfull + let valid_path = path_2.clone() + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id_2, + job_id = %job_id_2, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; + print_success( + true, + false, + &job_id_2, + &w_id_2, + &display_name_2, + name_tl, + counter_arc, + total_to_install, + start, + db_2, + ) + .await; + return; + } + } + + let Some(child) = child else { + let mut lock = not_pulled.write().await; + lock.push(RequiredDependency { + path: path_2.clone(), + custom_name: custom_name.clone(), + short_name: short_name.clone(), + }); + return; + }; + if let Err(e) = crate::handle_child::handle_child( + &job_id_2, + &db_2, + // TODO: Return mem_peak + &mut 0, + // TODO: Return canceld_by_ref + &mut None, + child, + !*DISABLE_NSJAIL, + &worker_name_2, + &w_id_2, + &installer_executable_name, + None, + false, + &mut None, + None, + ) + .await + { + windmill_queue::append_logs( + &job_id_2, + &w_id_2, + format!("error while installing {}: {e:?}", &display_name_2), + db_2.clone(), + ) + .await; + } else { + // if let Some(cb) = postinstall_cb { + // if let Err(e) = cb(vec![RequiredDependency { + // path: path_2.clone(), + // custom_name: custom_name.clone(), + // short_name: short_name.clone(), + // }]) + // .await + // { + // tracing::error!( + // workspace_id = %w_id_2, + // job_id = %job_id_2, + // "Postinstall callback failed!\n{e}\n + // This might affect execution", + // ); + // } + // } + print_success( + false, + true, + &job_id_2, + &w_id_2, + &display_name_2, + name_tl, + counter_arc, + total_to_install, + start, + db_2, + ) + .await; + // TODO: Refactor + // Create a file to indicate that installation was successfull + let valid_path = path_2.clone() + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id_2, + job_id = %job_id_2, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; + #[cfg(all(feature = "enterprise", feature = "parquet"))] + { + if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS + .read() + .await + .clone() + { + tokio::spawn(async move { + if let Err(e) = crate::global_cache::build_tar_and_push( + os, + path_2, + language_name, + custom_name, + platform_agnostic, + ) + .await + { + tracing::warn!("failed to build tar and push: {e:?}"); + } + }); + } + } + } + }); + handles.push(handle); + } + + for handle in handles { + if let Err(e) = handle.await { + tracing::error!("Error joining handles: {e:?}"); + } + } + if !not_pulled.read().await.is_empty() { + if let InstallStrategy::AllAtOnce(ref callback, ..) = install_fn { + let not_pulled_copy = not_pulled.read().await.clone(); + windmill_queue::append_logs( + job_id, + w_id, + format!("\n\nFetching {} packages...\n", not_pulled_copy.len()), + db.clone(), + ) + .await; + let cmd = callback(not_pulled_copy.clone())?; + tracing::debug!("{:?}", &cmd); + let child = start_child_process(cmd, &installer_executable_name).await?; + let mut buf = "".to_owned(); + let pipe_stdout = if stdout_on_err { Some(&mut buf) } else { None }; + if let Err(e) = crate::handle_child::handle_child( + // &job_id, + &Uuid::nil(), + &db, + // TODO: Return mem_peak + &mut 0, + // TODO: Return canceld_by_ref + &mut None, + child, + !*DISABLE_NSJAIL, + &worker_name, + &w_id, + &installer_executable_name, + None, + false, + &mut None, + pipe_stdout, + ) + .await + { + bail!(format!( + "error while installing dependencies: {e:?}\n{}", + buf + )); + } + { + postinstall_cb(not_pulled_copy.clone()).await?; + } + for RequiredDependency { path, custom_name: _custom_name, .. } in + not_pulled_copy.into_iter() + { + // TODO: Refactor + // Create a file to indicate that installation was successfull + let valid_path = path.clone() + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id, + job_id = %job_id, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; + #[cfg(all(feature = "enterprise", feature = "parquet"))] + { + if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS + .read() + .await + .clone() + { + let language_name = language_name.to_owned(); + tokio::spawn(async move { + if let Err(e) = crate::global_cache::build_tar_and_push( + os, + path, + language_name, + _custom_name, + platform_agnostic, + ) + .await + { + tracing::warn!("failed to build tar and push: {e:?}"); + } + }); + } + } + } + } + } + { + let total_time = total_time.elapsed().as_millis(); + windmill_queue::append_logs( + &job_id, + w_id, + format!( + "\nDone. Time spent on installation phase: {}ms\n", + total_time + ), + db, + ) + .await; + } + Ok(()) +} diff --git a/backend/windmill-worker/src/csharp_executor.rs b/backend/windmill-worker/src/csharp_executor.rs index 9acb32b684..f30d653ff5 100644 --- a/backend/windmill-worker/src/csharp_executor.rs +++ b/backend/windmill-worker/src/csharp_executor.rs @@ -126,6 +126,7 @@ pub async fn generate_nuget_lockfile( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -381,6 +382,7 @@ async fn build_cs_proj( None, false, &mut Some(occupancy_metrics), + None, ) .await?; append_logs(job_id, w_id, "\n\n", db).await; @@ -400,6 +402,7 @@ async fn build_cs_proj( &bin_path, &format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}"), &target, + false, ) .await { @@ -474,7 +477,8 @@ pub async fn handle_csharp_job( 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, cache_logs) = + windmill_common::worker::load_cache(&bin_path, &remote_path, false).await; let cache_logs = if cache { #[cfg(unix)] @@ -634,6 +638,7 @@ pub async fn handle_csharp_job( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_result(job_dir).await diff --git a/backend/windmill-worker/src/deno_executor.rs b/backend/windmill-worker/src/deno_executor.rs index 2b93c86ab4..3e7f7f2bde 100644 --- a/backend/windmill-worker/src/deno_executor.rs +++ b/backend/windmill-worker/src/deno_executor.rs @@ -156,6 +156,7 @@ pub async fn generate_deno_lock( None, false, occupancy_metrics, + None, ) .await?; } else { @@ -414,6 +415,7 @@ try {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; // logs.push_str(format!("execute: {:?}\n", start.elapsed().as_millis()).as_str()); diff --git a/backend/windmill-worker/src/global_cache.rs b/backend/windmill-worker/src/global_cache.rs index 23ce255615..6d0159d64e 100644 --- a/backend/windmill-worker/src/global_cache.rs +++ b/backend/windmill-worker/src/global_cache.rs @@ -17,8 +17,9 @@ pub const TARGET: &str = const_format::concatcp!(std::env::consts::OS, "_", std: pub async fn build_tar_and_push( s3_client: Arc, folder: String, - // python_311 - python_xyz: String, + lang: String, + custom_folder_name: Option, + platform_agnostic: bool, ) -> error::Result<()> { use object_store::path::Path; @@ -28,9 +29,13 @@ pub async fn build_tar_and_push( let start = Instant::now(); // e.g. tiny==1.0.0 - let folder_name = folder.split("/").last().unwrap(); + let folder_name = if let Some(name) = custom_folder_name { + name + } else { + folder.split("/").last().unwrap().to_owned() + }; - let prefix = &format!("{TAR_PYBASE_CACHE_DIR}/{}", python_xyz); + let prefix = &format!("{TAR_PYBASE_CACHE_DIR}/{}", lang); let tar_path = format!("{prefix}/{folder_name}_tar.tar",); let tar_file = std::fs::File::create(&tar_path)?; @@ -51,7 +56,10 @@ pub async fn build_tar_and_push( // })?; if let Err(e) = s3_client .put( - &Path::from(format!("/tar/{TARGET}/{python_xyz}/{folder_name}.tar")), + &Path::from(format!( + "/tar/{}/{lang}/{folder_name}.tar", + if platform_agnostic { "" } else { TARGET } + )), std::fs::read(&tar_path)?.into(), ) .await @@ -79,18 +87,26 @@ pub async fn build_tar_and_push( pub async fn pull_from_tar( client: Arc, folder: String, - // python_311 - python_xyz: String, + lang: String, + custom_folder_name: Option, + platform_agnostic: bool, ) -> error::Result<()> { use windmill_common::s3_helpers::attempt_fetch_bytes; - let folder_name = folder.split("/").last().unwrap(); + let folder_name = if let Some(name) = custom_folder_name { + name + } else { + folder.split("/").last().unwrap().to_owned() + }; - tracing::info!("Attempting to pull piptar {folder_name} from bucket"); + tracing::info!("Attempting to pull tar {folder_name} from bucket"); let start = Instant::now(); - let tar_path = format!("tar/{TARGET}/{python_xyz}/{folder_name}.tar"); + let tar_path = format!( + "tar/{}/{lang}/{folder_name}.tar", + if platform_agnostic { "" } else { TARGET } + ); let bytes = attempt_fetch_bytes(client, &tar_path).await?; extract_tar(bytes, &folder).map_err(|e| { diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index 43e88c59aa..959845f641 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -65,7 +65,8 @@ pub async fn handle_go_job( )); let bin_path = format!("{}/{hash}", GO_BIN_CACHE_DIR); let remote_path = format!("{GO_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await; + let (cache, cache_logs) = + windmill_common::worker::load_cache(&bin_path, &remote_path, false).await; let (skip_go_mod, skip_tidy) = if cache { (true, true) @@ -212,6 +213,7 @@ func Run(req Req) (interface{{}}, error){{ None, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -219,6 +221,7 @@ func Run(req Req) (interface{{}}, error){{ &bin_path, &format!("{GO_OBJECT_STORE_PREFIX}{hash}"), &format!("{job_dir}/main"), + false, ) .await { @@ -314,6 +317,7 @@ func Run(req Req) (interface{{}}, error){{ job.timeout, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -378,6 +382,7 @@ pub async fn install_go_dependencies( None, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -444,6 +449,7 @@ pub async fn install_go_dependencies( None, false, &mut Some(occupation_metrics), + None, ) .await?; diff --git a/backend/windmill-worker/src/handle_child.rs b/backend/windmill-worker/src/handle_child.rs index 7294950363..19c7d53bf7 100644 --- a/backend/windmill-worker/src/handle_child.rs +++ b/backend/windmill-worker/src/handle_child.rs @@ -103,6 +103,8 @@ pub async fn handle_child( custom_timeout: Option, sigterm: bool, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, + // Do not print logs to output, but instead save to string. + pipe_stdout: Option<&mut String>, ) -> error::Result<()> { let start = Instant::now(); @@ -304,6 +306,8 @@ pub async fn handle_child( let mut log_total_size: u64 = 0; let pg_log_total_size = Arc::new(AtomicU32::new(0)); + let mut pipe_stdout = pipe_stdout; + while let Some(line) = output.by_ref().next().await { let do_write_ = do_write.shared(); @@ -385,9 +389,12 @@ pub async fn handle_child( let worker_name = worker.to_string(); let w_id2 = w_id.to_string(); - (do_write, write_result) = tokio::spawn(append_job_logs(job_id, w_id2, joined, db.clone(), compact_logs, pg_log_total_size.clone(), worker_name)).remote_handle(); - - + if let Some(buf) = &mut pipe_stdout { + buf.push_str(&joined); + (do_write, write_result) = tokio::spawn(async { }).remote_handle(); + } else { + (do_write, write_result) = tokio::spawn(append_job_logs(job_id, w_id2, joined, db.clone(), compact_logs, pg_log_total_size.clone(), worker_name)).remote_handle(); + } if let Err(err) = result { tracing::error!(%job_id, %err, "error reading output for job {job_id} '{child_name}': {err}"); diff --git a/backend/windmill-worker/src/java_executor.rs b/backend/windmill-worker/src/java_executor.rs new file mode 100644 index 0000000000..6f593561d2 --- /dev/null +++ b/backend/windmill-worker/src/java_executor.rs @@ -0,0 +1,915 @@ +use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc}; + +use anyhow::{anyhow, bail}; +use async_recursion::async_recursion; +use itertools::Itertools; +use serde_json::value::RawValue; +use tokio::{ + fs::{create_dir_all, metadata, remove_dir_all, File}, + io::AsyncWriteExt, + process::Command, +}; +use uuid::Uuid; +use windmill_common::{ + error::{self, Error}, + utils::calculate_hash, + worker::{copy_dir_recursively, save_cache, write_file}, +}; +use windmill_parser::Arg; +use windmill_parser_java::parse_java_sig_meta; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; + +use crate::{ + common::{ + create_args_and_out_file, get_reserved_variables, par_install_language_dependencies, + read_result, start_child_process, OccupancyMetrics, RequiredDependency, + }, + handle_child, AuthedClient, COURSIER_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, JAVA_CACHE_DIR, + JAVA_REPOSITORY_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, +}; +lazy_static::lazy_static! { + static ref JAVA_CONCURRENT_DOWNLOADS: usize = std::env::var("JAVA_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); + static ref JAVA_PATH: String = std::env::var("JAVA_PATH").unwrap_or_else(|_| "/usr/bin/java".to_string()); + static ref JAVAC_PATH: String = std::env::var("JAVAC_PATH").unwrap_or_else(|_| "/usr/bin/javac".to_string()); + static ref CS_PATH: String = std::env::var("COURSIER_PATH").unwrap_or_else(|_| "/usr/bin/coursier".to_string()); + static ref STOREPASS: String = std::env::var("JAVA_STOREPASS").unwrap_or("123456".into()); + static ref TRUST_STORE_PATH: String = std::env::var("JAVA_TRUST_STORE_PATH").unwrap_or("/usr/local/share/ca-certificates/truststore.jks".into()); +} + +const NSJAIL_CONFIG_RUN_JAVA_CONTENT: &str = include_str!("../nsjail/run.java.config.proto"); + +#[allow(dead_code)] +pub(crate) struct JobHandlerInput<'a> { + pub base_internal_url: &'a str, + pub canceled_by: &'a mut Option, + pub client: &'a AuthedClient, + pub parent_runnable_path: Option, + pub db: &'a sqlx::Pool, + pub envs: HashMap, + pub inner_content: &'a str, + pub job: &'a MiniPulledJob, + pub job_dir: &'a str, + pub mem_peak: &'a mut i32, + pub occupancy_metrics: &'a mut OccupancyMetrics, + pub requirements_o: Option<&'a String>, + pub shared_mount: &'a str, + pub worker_name: &'a str, +} + +pub async fn handle_java_job<'a>(mut args: JobHandlerInput<'a>) -> Result, Error> { + // --- Prepare --- + { + prepare(&mut args).await?; + } + // --- Generate Lockfile --- + + let deps = resolve( + &args.job.id, + &args.inner_content, + &args.job_dir, + &args.db, + &args.job.workspace_id, + ) + .await?; + + // --- Install --- + + let classpath = install(&mut args, deps).await?; + + // --- Build .java files --- + { + compile(&mut args, &classpath).await?; + } + // --- Run --- + { + run(&mut args, &classpath).await?; + } + // --- Retrieve results --- + { + read_result(&args.job_dir).await + } +} + +async fn prepare<'a>( + JobHandlerInput { job, db, job_dir, client, inner_content, .. }: &mut JobHandlerInput<'a>, +) -> Result<(), Error> { + // Create needed files + { + create_args_and_out_file(&client, job, job_dir, db).await?; + let app_path = format!("{}/src/main/java/net/script/", job_dir); + create_dir_all(&app_path).await?; + File::create(format!("{app_path}/App.java")) + .await? + .write_all(&wrap(inner_content)?.into_bytes()) + .await?; + File::create(format!("{app_path}/Main.java")) + .await? + .write_all( + &format!( + "package net.script;\n{MINI_CLIENT_IMPORTS}\n{}\n{MINI_CLIENT}", + inner_content + ) + .into_bytes(), + ) + .await?; + } + Ok(()) +} + +pub async fn resolve<'a>( + job_id: &Uuid, + code: &str, + job_dir: &str, + db: &sqlx::Pool, + w_id: &str, +) -> Result { + let deps = { + let find_requirements = code.lines().find_position(|x| { + x.starts_with("//requirements:") || x.starts_with("// requirements:") + }); + + let specified_deps = if let Some((pos, _)) = find_requirements { + code.lines() + .skip(pos + 1) + .map_while(|x| { + if x.starts_with("//") { + Some(x.replace("//", "").trim().to_owned()) + } else { + None + } + }) + .collect::>() + } else { + Default::default() + }; + + let mut deps = vec![ + // Default requirements + "com.fasterxml.jackson.core:jackson-databind:2.9.8".to_owned(), + ]; + deps.extend(specified_deps); + deps.join("\n") + }; + + let req_hash = format!("java-{}", calculate_hash(&deps)); + if let Some(cached) = sqlx::query_scalar!( + "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", + req_hash + ) + .fetch_optional(db) + .await? + { + return Ok(cached); + } + let lock = { + append_logs( + job_id, + w_id, + format!("\n--- RESOLVING LOCKFILE ---\n"), + db.clone(), + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "java" + } else { + JAVA_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .envs(PROXY_ENVS.clone()); + + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + cmd.args(&[ + "-jar", + &CS_PATH, + "resolve", + &get_no_default(), + "--parallel", + &format!("{}", *JAVA_CONCURRENT_DOWNLOADS), + "--cache", + COURSIER_CACHE_DIR, + ]) + .args(&get_repos().await) + .args(&deps.split("\n").collect_vec()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + let output = cmd.output().await?; + // Check if the command was successful + if output.status.success() { + String::from_utf8(output.stdout).expect("Failed to convert output to String") + } else { + let stderr = + String::from_utf8(output.stderr).expect("Failed to convert error output to String"); + return Err(error::Error::internal_err(stderr)); + } + }; + + sqlx::query!( + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + req_hash, + lock.clone(), + ).fetch_optional(db).await?; + + append_logs(job_id, w_id, format!("\n{}", &lock), db.clone()).await; + Ok(lock) +} + +async fn install<'a>( + JobHandlerInput { worker_name, job, db, job_dir, .. }: &mut JobHandlerInput<'a>, + deps: String, +) -> Result { + let deps = deps + .lines() + .map(|line| { + let unparsed_dep = line.replace(":jar", "").replace(":lib", ""); + let mut it = unparsed_dep.split(":"); + + match (it.next(), it.next(), it.next()) { + (Some(group_id), Some(artifact_id), Some(version)) => { + let path = format!( + "{JAVA_REPOSITORY_DIR}/{}/{artifact_id}/{version}", + group_id.replace(".", "/") + ); + Ok(RequiredDependency { + path, + custom_name: Some(format!("{group_id}:{artifact_id}:{version}")), + short_name: Some(format!("{artifact_id}:{version}")), + }) + } + _ => anyhow::bail!("{line} is not parsable"), + } + }) + .collect::>>()?; + + let classpath = deps + .clone() + .into_iter() + .map(|RequiredDependency { path, .. }| path + "/*") + .collect_vec() + .join(":") + + ":target"; + + #[cfg(windows)] + let classpath = classpath.replace(":", ";"); + + tracing::debug!( + workspace_id = %job.workspace_id, + "JAVA classpath: {}", &classpath + ); + let (repos, no_default, trust_store_metadata) = ( + get_repos().await, + get_no_default(), + metadata(TRUST_STORE_PATH.clone()).await, + ); + let job_dir = job_dir.to_owned(); + let fetch_dir = format!("{JAVA_CACHE_DIR}/tmp-fetch-{}", Uuid::new_v4()); + let fetch_dir2 = fetch_dir.clone(); + par_install_language_dependencies( + deps, + "java", + "java", + true, + *JAVA_CONCURRENT_DOWNLOADS, + true, + crate::common::InstallStrategy::AllAtOnce(Arc::new(move |dependencies| { + let mut cmd = Command::new(if cfg!(windows) { + "java" + } else { + JAVA_PATH.as_str() + }); + let artifacts = dependencies + .into_iter() + .map(|e| { + e.custom_name.ok_or(anyhow::anyhow!( + "Internal Error: Artifact name should be Some!" + )) + }) + .collect::>>()?; + cmd.env_clear() + .current_dir(&job_dir) + .env("PATH", PATH_ENV.as_str()) + .envs(PROXY_ENVS.clone()); + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + + if trust_store_metadata.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + cmd.args(&[ + "-jar", + &CS_PATH, + "fetch", + &no_default, + "--quiet", + "--parallel", + &format!("{}", *JAVA_CONCURRENT_DOWNLOADS), + "--cache", + &fetch_dir, + ]) + .args(&repos) + .arg("--intransitive") + .args(artifacts) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + + Ok(cmd) + })), + async move |_| { + move_to_repository(&fetch_dir2, 0).await?; + remove_dir_all(&fetch_dir2).await?; + #[async_recursion] + async fn move_to_repository(path: &str, depth: u8) -> anyhow::Result<()> { + if depth == 3 { + copy_dir_recursively( + &PathBuf::from(path), + &PathBuf::from(JAVA_REPOSITORY_DIR), + )?; + + return Ok(()); + } + let mut entries = tokio::fs::read_dir(path).await?; + loop { + let Some(entry) = entries.next_entry().await? else { + break Ok(()); + }; + + let path = entry + .path() + .to_str() + .ok_or(anyhow!("Internal Error: Cannot convert Path to Str"))? + .to_owned(); + + move_to_repository(&path, depth + 1).await?; + } + } + Ok(()) + }, + &job.id, + &job.workspace_id, + worker_name, + db, + ) + .await?; + Ok(classpath) +} + +async fn compile<'a>( + JobHandlerInput { + occupancy_metrics, + mem_peak, + canceled_by, + worker_name, + job, + db, + job_dir, + client, + envs, + base_internal_url, + inner_content, + requirements_o, + parent_runnable_path, + .. + }: &mut JobHandlerInput<'a>, + classpath: &'a str, + // plugins: Vec<&'a str>, +) -> Result<(), Error> { + fn compute_hash(code: &str, requirements_o: Option<&String>) -> String { + calculate_hash(&format!( + "{}{}", + code, + requirements_o + .as_ref() + .map(|x| x.to_string()) + .unwrap_or_default() + )) + } + let reserved_variables = + get_reserved_variables(job, &client.token, db, parent_runnable_path.clone()).await?; + let hash = compute_hash(inner_content, *requirements_o); + let bin_path = format!("{}/{hash}", JAVA_CACHE_DIR); + let remote_path = format!("java_jar/{hash}"); + let (cache, ..) = windmill_common::worker::load_cache(&bin_path, &remote_path, true).await; + + if cache { + let target = format!("{job_dir}/target"); + + #[cfg(unix)] + let symlink = std::os::unix::fs::symlink(&bin_path, &target); + #[cfg(windows)] + let symlink = copy_dir_recursively(&PathBuf::from(&bin_path), &PathBuf::from(&target)); + + symlink.map_err(|e| { + Error::ExecutionErr(format!( + "could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}" + )) + })?; + } else { + // let plugin_registry = format!("{job_dir}/plugin-registry"); + let child = { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- COMPILING .JAVA FILES\n"), + db.clone(), + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "javac" + } else { + JAVAC_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables) + .envs(PROXY_ENVS.clone()) + .args(&[ + "-classpath", + &classpath, + "src/main/java/net/script/Main.java", + "src/main/java/net/script/App.java", + "-d", + "./target", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + start_child_process(cmd, "javac").await? + }; + handle_child::handle_child( + &job.id, + db, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + &job.workspace_id, + "javac", + job.timeout, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + match save_cache( + &bin_path, + &format!("java_jar/{hash}"), + &format!("{job_dir}/target"), + true, + ) + .await + { + Err(e) => { + let em = format!( + "could not save {bin_path} to {} to java cache: {e:?}", + format!("{job_dir}/main"), + ); + tracing::error!(em); + } + Ok(logs) => { + tracing::trace!(logs); + } + } + }; + + Ok(()) +} +async fn run<'a>( + JobHandlerInput { + occupancy_metrics, + mem_peak, + canceled_by, + worker_name, + job, + db, + job_dir, + shared_mount, + client, + envs, + base_internal_url, + parent_runnable_path, + .. + }: &mut JobHandlerInput<'a>, + classpath: &'a str, +) -> Result<(), Error> { + let reserved_variables = + get_reserved_variables(job, &client.token, db, parent_runnable_path.clone()).await?; + + let child = if !cfg!(windows) && !*DISABLE_NSJAIL { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- ISOLATED JAVA CODE EXECUTION ---\n"), + db.clone(), + ) + .await; + + write_file( + job_dir, + "run.config.proto", + &NSJAIL_CONFIG_RUN_JAVA_CONTENT + .replace("{JOB_DIR}", job_dir) + .replace("{CACHE_DIR}", JAVA_CACHE_DIR) + .replace("{SHARED_MOUNT}", &shared_mount) + // .replace("{CACHED_TARGET}", &shared_mount) + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), + )?; + let mut cmd = Command::new(NSJAIL_PATH.as_str()); + cmd.env_clear() + .current_dir(job_dir) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables) + .envs(PROXY_ENVS.clone()) + .args(vec![ + "--config", + "run.config.proto", + "--", + JAVA_PATH.as_str(), + ]); + if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + cmd.args(vec!["-classpath", &classpath, "net.script.App"]); + cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); + + start_child_process(cmd, NSJAIL_PATH.as_str()).await? + } else { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- JAVA CODE EXECUTION ---\n"), + db.clone(), + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "java" + } else { + JAVA_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables); + if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + cmd.args(&["-classpath", &classpath, "net.script.App"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + start_child_process(cmd, "java").await? + }; + handle_child::handle_child( + &job.id, + db, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + &job.workspace_id, + "java", + job.timeout, + false, + &mut Some(occupancy_metrics), + None, + ) + .await +} + +#[derive(Default, Debug)] +struct JavaProxySettings { + http_host: Option, + http_port: Option, + https_host: Option, + https_port: Option, + no_proxy: Option, +} +fn parse_proxy() -> anyhow::Result { + let mut jps = JavaProxySettings::default(); + for (ident, mut val) in PROXY_ENVS.clone() { + match ident { + "HTTPS_PROXY" => { + if val.contains("http://") { + bail!("HTTPS_PROXY url cannot contain http scheme."); + } + if !val.contains("https://") { + val = format!("https://{val}"); + } + let mut url = url::Url::parse(&val)?; + let port = url.port(); + // Make sure port and schema is not included in final url + { + url.set_port(None).unwrap_or_default(); + jps.https_host = Some(url.as_str().replace("https://", "")); + if let Some(port) = port { + jps.https_port = Some(format!("{}", port)); + } + } + } + "HTTP_PROXY" => { + if val.contains("https://") { + bail!("HTTP_PROXY url cannot contain https scheme."); + } + if !val.contains("http://") { + val = format!("http://{val}"); + } + let mut url = url::Url::parse(&val)?; + let port = url.port(); + // Make sure port and schema is not included in final url + { + url.set_port(None).unwrap_or_default(); + jps.http_host = Some(url.as_str().replace("http://", "")); + if let Some(port) = port { + jps.https_port = Some(format!("{}", port)); + } + } + } + // Java uses | instead of , + "NO_PROXY" => jps.no_proxy = Some(val.replace(",", "|")), + _ => {} + } + } + + Ok(jps) +} +async fn get_repos() -> Vec { + MAVEN_REPOS + .read() + .await + .as_ref() + .map(|repos| { + repos + .trim() + .split_whitespace() + .into_iter() + .map(|el| vec!["--repository".to_owned(), el.to_owned()]) + .collect_vec() + }) + .unwrap_or_default() + .concat() +} + +fn get_no_default() -> String { + if NO_DEFAULT_MAVEN.load(std::sync::atomic::Ordering::Relaxed) { + "--no-default" + } else { + // Command does not take empty arguments + "-q" + } + .into() +} + +/// Wraps content script +/// that upon execution reads args.json (which are piped and transformed from previous flow step or top level inputs) +/// Also wrapper takes output of program and serializes to result.json (Which windmill will know how to use later) +fn wrap(inner_content: &str) -> Result { + let sig = parse_java_sig_meta(inner_content)?; + let ret_void = sig.returns_void; + let spread = sig + .main_sig + .args + .clone() + .into_iter() + .map(|Arg { name, .. }| { + // Apply additional input transformation + format!(" parsedArgs.{name}") + }) + .collect_vec() + .join(","); + let args = sig + .main_sig + .args + .clone() + .into_iter() + .map(|Arg { name, otyp, .. }| { + // Apply additional input transformation + format!("public {} {name};\n", otyp.unwrap()) + }) + .collect_vec() + .join(" "); + Ok(r#" +package net.script; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.FileOutputStream; +import net.script.Main; + +public class App{ + + public static class Args {ARGS} + + public static void main(String[] args) { + try { + InputStream fileInputStream = new FileInputStream("args.json"); + ObjectMapper mapper = new ObjectMapper(); + Args parsedArgs = mapper.readValue(fileInputStream, Args.class); + fileInputStream.close(); + {MAIN_HANDLER} + FileOutputStream fileOutputStream = new FileOutputStream("result.json"); + mapper.writeValue(fileOutputStream, res); + fileOutputStream.close(); + + } catch (Exception e) { // Catching general Exception + e.printStackTrace(); // Handle the exception + } + } +} + "# + .replace( + "{MAIN_HANDLER}", + if ret_void { + " + Main.main(SPREAD); + Object res = null; + " + } else { + " + Object res = Main.main(SPREAD); + " + }, + ) + .replace("SPREAD", &spread) + .replace("ARGS", &args)) +} +const MINI_CLIENT_IMPORTS: &str = r#" +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +"#; +const MINI_CLIENT: &str = r#" +class Wmill { + public static String getVariable(String path) { + var baseUrl = System.getenv("BASE_INTERNAL_URL"); + var workspace = System.getenv("WM_WORKSPACE"); + var uri = java.text.MessageFormat.format("{0}/api/w/{1}/variables/get_value/{2}", baseUrl, workspace, path); + + // Create an HttpRequest + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(uri)) + .header("Authorization", "Bearer " + System.getenv("WM_TOKEN")) // Add the Authorization header + .GET() // Set the request method to GET + .build(); + + // Send the request and get the response + return HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .join(); // Wait for the completion + } + public static String getResource(String path) { + var baseUrl = System.getenv("BASE_INTERNAL_URL"); + var workspace = System.getenv("WM_WORKSPACE"); + var uri = java.text.MessageFormat.format("{0}/api/w/{1}/resources/get_value_interpolated/{2}", baseUrl, workspace, path); + + // Create an HttpRequest + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(uri)) + .header("Authorization", "Bearer " + System.getenv("WM_TOKEN")) // Add the Authorization header + .GET() // Set the request method to GET + .build(); + + // Send the request and get the response + return HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .join(); // Wait for the completion + } +} +"#; diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index ba47fd81fc..607616117f 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -11,6 +11,10 @@ mod bash_executor; #[cfg(feature = "nu")] mod nu_executor; + +#[cfg(feature = "java")] +mod java_executor; + #[cfg(feature = "benchmark")] pub mod bench; mod bun_executor; @@ -40,11 +44,11 @@ mod python_executor; mod result_processor; #[cfg(feature = "rust")] mod rust_executor; +mod sanitized_sql_params; +mod schema; mod worker; mod worker_flow; mod worker_lockfiles; -mod sanitized_sql_params; -mod schema; pub use worker::*; diff --git a/backend/windmill-worker/src/nu_executor.rs b/backend/windmill-worker/src/nu_executor.rs index e40e1029f4..729302b7c3 100644 --- a/backend/windmill-worker/src/nu_executor.rs +++ b/backend/windmill-worker/src/nu_executor.rs @@ -334,6 +334,7 @@ async fn run<'a>( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await } diff --git a/backend/windmill-worker/src/php_executor.rs b/backend/windmill-worker/src/php_executor.rs index 19dce7f1b9..4707c8db16 100644 --- a/backend/windmill-worker/src/php_executor.rs +++ b/backend/windmill-worker/src/php_executor.rs @@ -105,6 +105,7 @@ pub async fn composer_install( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -339,6 +340,7 @@ try {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_result(job_dir).await diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 648c40a9a3..e3bfe6838b 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -27,7 +27,7 @@ use windmill_common::{ Error::{self}, }, utils::calculate_hash, - worker::{write_file, PythonAnnotations, WORKER_CONFIG}, + worker::{copy_dir_recursively, pad_string, write_file, PythonAnnotations, WORKER_CONFIG}, DB, }; @@ -49,6 +49,7 @@ lazy_static::lazy_static! { static ref PY_CONCURRENT_DOWNLOADS: usize = var("PY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); + static ref NON_ALPHANUM_CHAR: Regex = regex::Regex::new(r"[^0-9A-Za-z=.-]").unwrap(); static ref TRUSTED_HOST: Option = var("PY_TRUSTED_HOST").ok().or(var("PIP_TRUSTED_HOST").ok()); @@ -350,6 +351,7 @@ impl PyVersion { None, false, occupancy_metrics, + None, ) .await } @@ -644,6 +646,7 @@ pub async fn uv_pip_compile( None, false, occupancy_metrics, + None, ) .await .map_err(|e| { @@ -780,30 +783,6 @@ async fn postinstall( Ok(()) } -fn copy_dir_recursively(src: &Path, dst: &Path) -> windmill_common::error::Result<()> { - if !dst.exists() { - fs::create_dir_all(dst)?; - } - - tracing::debug!("Copying recursively from {:?} to {:?}", src, dst); - - for entry in fs::read_dir(src)? { - let entry = entry?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); - - if src_path.is_dir() && !src_path.is_symlink() { - copy_dir_recursively(&src_path, &dst_path)?; - } else { - fs::copy(&src_path, &dst_path)?; - } - } - - tracing::debug!("Finished copying recursively from {:?} to {:?}", src, dst); - - Ok(()) -} - async fn get_python_path( py_version: PyVersion, worker_name: &str, @@ -1161,6 +1140,7 @@ mount {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -1709,19 +1689,6 @@ async fn spawn_uv_install( } } -/// length = 5 -/// value = "foo" -/// output = "foo " -/// 12345 -fn pad_string(value: &str, total_length: usize) -> String { - if value.len() >= total_length { - value.to_string() // Return the original string if it's already long enough - } else { - let padding_needed = total_length - value.len(); - format!("{value}{}", " ".repeat(padding_needed)) // Pad with spaces - } -} - /// uv pip install, include cached or pull from S3 pub async fn handle_python_reqs( requirements: Vec<&str>, @@ -2053,7 +2020,7 @@ pub async fn handle_python_reqs( tokio::select! { // Cancel was called on the job _ = kill_rx.recv() => return Err(anyhow::anyhow!("S3 pull was canceled")), - pull = pull_from_tar(os, venv_p.clone(), py_version.to_cache_dir_top_level()) => { + pull = pull_from_tar(os, venv_p.clone(), py_version.to_cache_dir_top_level(), None, false) => { if let Err(e) = pull { tracing::info!( workspace_id = %w_id, @@ -2204,7 +2171,7 @@ pub async fn handle_python_reqs( #[cfg(all(feature = "enterprise", feature = "parquet", unix))] if s3_push { if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { - tokio::spawn(build_tar_and_push(os, venv_p.clone(), py_version.to_cache_dir_top_level())); + tokio::spawn(build_tar_and_push(os, venv_p.clone(), py_version.to_cache_dir_top_level(), None, false)); } } diff --git a/backend/windmill-worker/src/rust_executor.rs b/backend/windmill-worker/src/rust_executor.rs index d2a0c3217d..21ec46422d 100644 --- a/backend/windmill-worker/src/rust_executor.rs +++ b/backend/windmill-worker/src/rust_executor.rs @@ -164,6 +164,7 @@ pub async fn generate_cargo_lockfile( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -226,6 +227,7 @@ pub async fn build_rust_crate( None, false, &mut Some(occupancy_metrics), + None, ) .await?; append_logs(job_id, w_id, "\n\n", db).await; @@ -245,6 +247,7 @@ pub async fn build_rust_crate( &bin_path, &format!("{RUST_OBJECT_STORE_PREFIX}{hash}"), &format!("{job_dir}/main"), + false, ) .await { @@ -294,7 +297,8 @@ pub async fn handle_rust_job( let bin_path = format!("{}/{hash}", RUST_CACHE_DIR); let remote_path = format!("{RUST_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await; + let (cache, cache_logs) = + windmill_common::worker::load_cache(&bin_path, &remote_path, false).await; let cache_logs = if cache { let target = format!("{job_dir}/main"); @@ -407,6 +411,7 @@ pub async fn handle_rust_job( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_result(job_dir).await diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index a1f1754ab5..f13d22fafe 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -123,7 +123,10 @@ use crate::{ use crate::rust_executor::handle_rust_job; #[cfg(feature = "nu")] -use crate::nu_executor::{handle_nu_job, JobHandlerInput}; +use crate::nu_executor::{handle_nu_job, JobHandlerInput as JobHandlerInputNu}; + +#[cfg(feature = "java")] +use crate::java_executor::{handle_java_job, JobHandlerInput as JobHandlerInputJava}; #[cfg(feature = "php")] use crate::php_executor::handle_php_job; @@ -264,6 +267,8 @@ pub const TAR_PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_311" pub const TAR_PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_312"); pub const TAR_PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_313"); +pub const TAR_JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/java"); + pub const UV_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "uv"); pub const PY_INSTALL_DIR: &str = concatcp!(ROOT_CACHE_DIR, "py_runtime"); pub const TAR_PYBASE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar"); @@ -275,6 +280,12 @@ pub const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go"); pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust"); pub const NU_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "nu"); pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp"); + +// JAVA +pub const JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "java"); +pub const COURSIER_CACHE_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/coursier-cache"); +pub const JAVA_REPOSITORY_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/repository"); +// KJQXZ 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_CODEBASE_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "script_bundle"); @@ -398,6 +409,11 @@ lazy_static::lazy_static! { pub static ref NPM_CONFIG_REGISTRY: Arc>> = Arc::new(RwLock::new(None)); pub static ref BUNFIG_INSTALL_SCOPES: Arc>> = Arc::new(RwLock::new(None)); pub static ref NUGET_CONFIG: Arc>> = Arc::new(RwLock::new(None)); + pub static ref MAVEN_REPOS: Arc>> = Arc::new(RwLock::new(None)); + pub static ref NO_DEFAULT_MAVEN: AtomicBool = AtomicBool::new(std::env::var("NO_DEFAULT_MAVEN") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(false)); pub static ref PIP_EXTRA_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); pub static ref PIP_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); @@ -3078,7 +3094,33 @@ mount {{ ); #[cfg(feature = "nu")] - handle_nu_job(JobHandlerInput { + handle_nu_job(JobHandlerInputNu { + mem_peak, + canceled_by, + job, + db, + client, + parent_runnable_path, + inner_content: &code, + job_dir, + requirements_o: lock.as_ref(), + shared_mount: &shared_mount, + base_internal_url, + worker_name, + envs, + occupancy_metrics, + }) + .await + } + Some(ScriptLang::Java) => { + #[cfg(not(feature = "java"))] + return Err(anyhow::anyhow!( + "Java is not available because the feature is not enabled" + ) + .into()); + + #[cfg(feature = "java")] + handle_java_job(JobHandlerInputJava { mem_peak, canceled_by, job, @@ -3163,6 +3205,11 @@ fn parse_sig_of_lang( ScriptLang::Nu => Some(windmill_parser_nu::parse_nu_signature(code)?), #[cfg(not(feature = "nu"))] ScriptLang::Nu => None, + #[cfg(feature = "java")] + ScriptLang::Java => Some(windmill_parser_java::parse_java_signature(code)?), + #[cfg(not(feature = "java"))] + ScriptLang::Java => None, + // KJQXZ } } else { None diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 17ac95ab3e..3b2d04c759 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -36,6 +36,9 @@ use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PushIsolationLevel} use crate::common::OccupancyMetrics; use crate::csharp_executor::generate_nuget_lockfile; +#[cfg(feature = "java")] +use crate::java_executor::resolve; + #[cfg(feature = "php")] use crate::php_executor::{composer_install, parse_php_imports}; #[cfg(feature = "python")] @@ -2076,16 +2079,17 @@ async fn capture_dependency_job( ) .await } - ScriptLang::Postgresql => Ok("".to_owned()), - ScriptLang::Mysql => Ok("".to_owned()), - ScriptLang::Bigquery => Ok("".to_owned()), - ScriptLang::Snowflake => Ok("".to_owned()), - ScriptLang::Mssql => Ok("".to_owned()), - ScriptLang::Graphql => Ok("".to_owned()), - ScriptLang::OracleDB => Ok("".to_owned()), - ScriptLang::Bash => Ok("".to_owned()), - ScriptLang::Nu => Ok("".to_owned()), - ScriptLang::Powershell => Ok("".to_owned()), - ScriptLang::Nativets => Ok("".to_owned()), + #[cfg(feature = "java")] + ScriptLang::Java => { + if raw_deps { + return Err(Error::ExecutionErr( + "Raw dependencies not supported for Java".to_string(), + )); + } + + resolve(job_id, job_raw_code, job_dir, db, w_id).await + } + // KJQXZ + _ => Ok("".to_owned()), } } diff --git a/cli/bootstrap/script_bootstrap.ts b/cli/bootstrap/script_bootstrap.ts index 5c2247e66b..d711496ba7 100644 --- a/cli/bootstrap/script_bootstrap.ts +++ b/cli/bootstrap/script_bootstrap.ts @@ -119,4 +119,12 @@ inventory: debug: msg: "Hello, world!" `, + java: ` +public class Main { + public static void main() { + System.out.println("Hello World"); + } +} +`, +// KJQXZ }; diff --git a/cli/dnt.ts b/cli/dnt.ts index 64d9957499..3820439a7e 100644 --- a/cli/dnt.ts +++ b/cli/dnt.ts @@ -69,6 +69,7 @@ await build({ "rust", "yaml", "csharp", + "java", ]; for (const l of dirs) { diff --git a/cli/gen/services.gen.ts b/cli/gen/services.gen.ts index bee9069a5b..6771928fd7 100644 --- a/cli/gen/services.gen.ts +++ b/cli/gen/services.gen.ts @@ -8652,4 +8652,4 @@ export const clearIndex = (data: ClearIndexData): CancelablePromise { throw Error('TextEncoder not available') } } ); + +const encodeString = function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +}; + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} +/** + * @param {string} code + * @returns {string} + */ +export function parse_java(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_java(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +const imports = { + __wbindgen_placeholder__: { + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; + }, + }, + +}; + +const wasm_url = new URL('windmill_parser_wasm_bg.wasm', import.meta.url); +let wasmCode = ''; +switch (wasm_url.protocol) { + case 'file:': + wasmCode = await Deno.readFile(wasm_url); + break + case 'https:': + case 'http:': + wasmCode = await (await fetch(wasm_url)).arrayBuffer(); + break + default: + throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); +} + +const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance; +const wasm = wasmInstance.exports; +export const __wasm = wasm; + +wasm.__wbindgen_start(); + diff --git a/cli/wasm/java/windmill_parser_wasm_bg.wasm b/cli/wasm/java/windmill_parser_wasm_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5395c1e7895a70ad3b375e3a63ba6d4e79f6058b GIT binary patch literal 499719 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 K>&zbx3A{&Op)+Ggw6Q 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 HY50EuXjkX^ literal 0 HcmV?d00001 diff --git a/cli/wasm/java/windmill_parser_wasm_bg.wasm.d.ts b/cli/wasm/java/windmill_parser_wasm_bg.wasm.d.ts new file mode 100644 index 0000000000..6128cc38c6 --- /dev/null +++ b/cli/wasm/java/windmill_parser_wasm_bg.wasm.d.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const parse_java: (a: number, b: number) => [number, number]; +export const abort: () => void; +export const malloc: (a: number) => number; +export const calloc: (a: number, b: number) => number; +export const realloc: (a: number, b: number) => number; +export const free: (a: number) => void; +export const strncmp: (a: number, b: number, c: number) => number; +export const iswspace: (a: number) => number; +export const iswalnum: (a: number) => number; +export const clock: () => bigint; +export const isprint: (a: number) => number; +export const fprintf: (a: number, b: number, c: number) => number; +export const fputs: (a: number, b: number) => number; +export const fputc: (a: number, b: number) => number; +export const fdopen: (a: number, b: number) => number; +export const fclose: (a: number) => number; +export const fwrite: (a: number, b: number, c: number, d: number) => number; +export const vsnprintf: (a: number, b: number, c: number, d: number) => number; +export const clock_gettime: (a: number, b: number) => void; +export const snprintf: () => void; +export const __assert_fail: (a: number, b: number, c: number, d: number) => void; +export const __wbindgen_export_0: WebAssembly.Table; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __wbindgen_start: () => void; diff --git a/docker/DockerfileFull b/docker/DockerfileFull index 7f904b01b7..c36682c4e9 100644 --- a/docker/DockerfileFull +++ b/docker/DockerfileFull @@ -14,3 +14,10 @@ ENV DOTNET_ROOT="/opt/dotnet-sdk/bin" # Nushell COPY --from=ghcr.io/nushell/nushell:0.101.0-bookworm /usr/bin/nu /usr/bin/nu + +# Java +RUN apt-get -y update && apt-get install -y default-jdk +RUN curl -fLo coursier https://github.com/coursier/coursier/releases/download/v2.1.24/coursier \ + && mv ./coursier /usr/bin/coursier \ + && chmod +x /usr/bin/coursier +RUN /usr/bin/java -jar /usr/bin/coursier about diff --git a/docker/DockerfileFullEe b/docker/DockerfileFullEe index b074bb4ecf..25cc47b2dd 100644 --- a/docker/DockerfileFullEe +++ b/docker/DockerfileFullEe @@ -37,3 +37,10 @@ RUN echo /opt/oracle/23/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && ldc # Nushell COPY --from=ghcr.io/nushell/nushell:0.101.0-bookworm /usr/bin/nu /usr/bin/nu + +# Java +RUN apt-get -y update && apt-get install -y default-jdk +RUN curl -fLo coursier https://github.com/coursier/coursier/releases/download/v2.1.24/coursier \ + && mv ./coursier /usr/bin/coursier \ + && chmod +x /usr/bin/coursier +RUN /usr/bin/java -jar /usr/bin/coursier about diff --git a/docker/DockerfileNsjail b/docker/DockerfileNsjail index d667b15722..e51e73a3ea 100644 --- a/docker/DockerfileNsjail +++ b/docker/DockerfileNsjail @@ -62,4 +62,12 @@ RUN echo /opt/oracle/23/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && ldc # Nushell COPY --from=ghcr.io/nushell/nushell:0.101.0-bookworm /usr/bin/nu /usr/bin/nu +# Java +RUN apt-get install -y default-jdk +RUN curl -fLo coursier https://github.com/coursier/coursier/releases/download/v2.1.24/coursier \ + && mv ./coursier /usr/bin/coursier \ + && chmod +x /usr/bin/coursier +RUN /usr/bin/java -jar /usr/bin/coursier about + COPY --from=nsjail /nsjail/nsjail /bin/nsjail + diff --git a/flake.nix b/flake.nix index 506e61d68e..8ded5a3ac9 100644 --- a/flake.nix +++ b/flake.nix @@ -31,6 +31,13 @@ pkg-config cmake ]; + coursier = pkgs.fetchFromGitHub { + owner = "coursier"; + repo = "launchers"; + rev = "79d927f7586c09ca6d8cd01862adb0d9f9d88dff"; + hash = "sha256-8E0WtDFc7RcqmftDigMyy1xXUkjgL4X4kpf7h1GdE48="; + }; + PKG_CONFIG_PATH = pkgs.lib.makeSearchPath "lib/pkgconfig" (with pkgs; [ openssl.dev libxml2.dev xmlsec.dev libxslt.dev ]); RUSTY_V8_ARCHIVE = let @@ -75,6 +82,7 @@ sqlx-cli flock sccache + nsjail deno python3 python3Packages.pip @@ -84,7 +92,9 @@ nushell dotnet-sdk_9 oracle-instantclient + # LSP/Local dev svelte-language-server + taplo ]); packages = [ (pkgs.writeScriptBin "wm-caddy" '' @@ -126,6 +136,25 @@ cd ./frontend npm run dev $* '') + (pkgs.writeScriptBin "wm-minio" '' + set -e + cd ./backend + mkdir -p .minio-data/wmill + ${pkgs.minio}/bin/minio server ./.minio-data + '') + # Generate keys + # TODO: Do not set new keys if ran multiple times + (pkgs.writeScriptBin "wm-minio-keys" '' + set -e + cd ./backend + ${pkgs.minio-client}/bin/mc alias set 'wmill-minio-dev' 'http://localhost:9000' 'minioadmin' 'minioadmin' + ${pkgs.minio-client}/bin/mc admin accesskey create myminio | tee .minio-data/secrets.txt + echo "" + echo 'Saving to: ./backend/.minio-data/secrets.txt' + echo "bucket: wmill" + echo "endpoint: http://localhost:9000" + '') + ]; inherit PKG_CONFIG_PATH RUSTY_V8_ARCHIVE; @@ -140,11 +169,16 @@ BUN_PATH = "${pkgs.bun}/bin/bun"; UV_PATH = "${pkgs.uv}/bin/uv"; NU_PATH = "${pkgs.nushell}/bin/nu"; + JAVA_PATH = "${pkgs.jdk21}/bin/java"; + JAVAC_PATH = "${pkgs.jdk21}/bin/javac"; + COURSIER_PATH = "${coursier}/coursier"; + # KJQXZ FLOCK_PATH = "${pkgs.flock}/bin/flock"; CARGO_PATH = "${rust}/bin/cargo"; DOTNET_PATH = "${pkgs.dotnet-sdk_9}/bin/dotnet"; DOTNET_ROOT = "${pkgs.dotnet-sdk_9}/share/dotnet"; ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib"; + RUST_LOG = "debug"; }; packages.default = self.packages.${system}.windmill; packages.windmill-client = pkgs.buildNpmPackage { @@ -218,7 +252,6 @@ "cloud" "jemalloc" "tantivy" - "deno_core" "license" "http_trigger" "zip" @@ -226,16 +259,10 @@ "kafka" "otel" "dind" - "php" - "mysql" - "mssql" - "bigquery" "websocket" - "python" "smtp" - "csharp" "static_frontend" - "rust" + "all_languages" ]; doCheck = false; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b31aeec507..613597544c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -68,6 +68,7 @@ "vscode-ws-jsonrpc": "~3.4.0", "windmill-parser-wasm-csharp": "^1.437.1", "windmill-parser-wasm-go": "^1.429.0", + "windmill-parser-wasm-java": "^1.478.1", "windmill-parser-wasm-nu": "^1.474.1", "windmill-parser-wasm-php": "^1.429.0", "windmill-parser-wasm-py": "^1.477.1", @@ -12528,6 +12529,11 @@ "resolved": "https://registry.npmjs.org/windmill-parser-wasm-go/-/windmill-parser-wasm-go-1.429.0.tgz", "integrity": "sha512-M3jeGDqeTyPj9HyyX3msdzMrqIIzlfMfxTMsXS8m7MJp4Cm60qifMxD29Ipxb2B4WdzyGwCSlaBjLsXu0b3c5g==" }, + "node_modules/windmill-parser-wasm-java": { + "version": "1.478.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-java/-/windmill-parser-wasm-java-1.478.1.tgz", + "integrity": "sha512-2gMr5pXaEExoLjeSMOpHzY4UjDU/JGjvlXSnBiQDHUaEoKSS7FWa5APzBKcmn8cM+mr2p8m4sMCyTXS88At0fQ==" + }, "node_modules/windmill-parser-wasm-nu": { "version": "1.474.1", "resolved": "https://registry.npmjs.org/windmill-parser-wasm-nu/-/windmill-parser-wasm-nu-1.474.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 489dba9869..d404efee87 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -144,6 +144,7 @@ "vscode-ws-jsonrpc": "~3.4.0", "windmill-parser-wasm-csharp": "^1.437.1", "windmill-parser-wasm-go": "^1.429.0", + "windmill-parser-wasm-java": "^1.478.1", "windmill-parser-wasm-nu": "^1.474.1", "windmill-parser-wasm-php": "^1.429.0", "windmill-parser-wasm-py": "^1.477.1", diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 807e16e69b..5b2aff62ed 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -195,6 +195,8 @@ | 'yaml' | 'csharp' | 'nu' + | 'java' + // KJQXZ export let code: string = '' export let cmdEnterAction: (() => void) | undefined = undefined export let formatAction: (() => void) | undefined = undefined diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index 3a778bfa66..9f785760af 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -93,7 +93,9 @@ 'php', 'rust', 'csharp', - 'nu' + 'nu', + 'java', + // KJQXZ ].includes(lang ?? '') $: showVarPicker = [ 'python3', @@ -107,7 +109,9 @@ 'php', 'rust', 'csharp', - 'nu' + 'nu', + 'java', + // KJQXZ ].includes(lang ?? '') $: showResourcePicker = [ 'python3', @@ -121,7 +125,9 @@ 'php', 'rust', 'csharp', - 'nu' + 'nu', + 'java', + // KJQXZ ].includes(lang ?? '') $: showResourceTypePicker = ['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) || @@ -373,6 +379,9 @@ editor.insertAtCursor(`Environment.GetEnvironmentVariable("${name}");`) } else if (lang == 'nu') { editor.insertAtCursor(`$env.${name}`) + } else if (lang == 'java') { + editor.insertAtCursor(`System.getenv("${name}");`) + // KJQXZ } sendUserToast(`${name} inserted at cursor`) }} @@ -440,6 +449,9 @@ string ${windmillPathToCamelCaseName(path)} = await client.GetStringAsync(uri); `) } else if (lang == 'nu') { editor.insertAtCursor(`get_variable ${path}`) + } else if (lang == 'java') { + editor.insertAtCursor(`(Wmill.getVariable("${path}"))`) + // KJQXZ } sendUserToast(`${name} inserted at cursor`) }} @@ -524,7 +536,11 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS `) } else if (lang == 'nu') { editor.insertAtCursor(`get_resource ${path}`) + } else if (lang == 'java') { + editor.insertAtCursor(`(Wmill.getResource("${path}"))`) + // KJQXZ } + sendUserToast(`${path} inserted at cursor`) }} tooltip="Resources represent connections to third party systems. Resources are a good way to define a connection to a frequently used third party system such as a database." diff --git a/frontend/src/lib/components/HighlightCode.svelte b/frontend/src/lib/components/HighlightCode.svelte index 87daeed695..d7bdc18314 100644 --- a/frontend/src/lib/components/HighlightCode.svelte +++ b/frontend/src/lib/components/HighlightCode.svelte @@ -12,6 +12,7 @@ import rust from 'svelte-highlight/languages/rust' import csharp from 'svelte-highlight/languages/csharp' import yaml from 'svelte-highlight/languages/yaml' + import java from 'svelte-highlight/languages/java' import type { Script } from '$lib/gen' import { Button } from './common' import { copyToClipboard } from '$lib/utils' @@ -65,7 +66,10 @@ case 'nu': return python case 'ansible': - return yaml + return yaml; + case 'java': + return java; + // KJQXZ default: return typescript } diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index eec97085fe..e68ee95c79 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -78,7 +78,7 @@ deno: false, go: false, ruff: false, - shellcheck: false + shellcheck: false, } const dispatch = createEventDispatcher() diff --git a/frontend/src/lib/components/WorkerGroup.svelte b/frontend/src/lib/components/WorkerGroup.svelte index 55878d4928..f64357e1cc 100644 --- a/frontend/src/lib/components/WorkerGroup.svelte +++ b/frontend/src/lib/components/WorkerGroup.svelte @@ -123,7 +123,9 @@ 'rust', 'ansible', 'csharp', - 'nu' + 'nu', + 'java' + // KJQXZ ] const nativeTags = [ 'nativets', diff --git a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte index 1c75ce39ed..38b3e12260 100644 --- a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte +++ b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte @@ -21,6 +21,7 @@ import AnsibleIcon from '$lib/components/icons/AnsibleIcon.svelte' import CSharpIcon from '$lib/components/icons/CSharpIcon.svelte' import NuIcon from '$lib/components/icons/NuIcon.svelte' + import JavaIcon from '$lib/components/icons/JavaIcon.svelte' export let lang: | SupportedLanguage @@ -56,7 +57,9 @@ rust: 'Rust', ansible: 'Ansible Playbook', csharp: 'C#', - nu: 'Nu' + nu: 'Nu', + java: 'Java' + // KJQXZ } const langToComponent: Record< @@ -87,7 +90,9 @@ rust: RustIcon, ansible: AnsibleIcon, csharp: CSharpIcon, - nu: NuIcon + nu: NuIcon, + java: JavaIcon + // KJQXZ } let subIconScale = width === 30 ? 0.6 : 0.8 diff --git a/frontend/src/lib/components/icons/JavaIcon.svelte b/frontend/src/lib/components/icons/JavaIcon.svelte new file mode 100644 index 0000000000..868aeeccaa --- /dev/null +++ b/frontend/src/lib/components/icons/JavaIcon.svelte @@ -0,0 +1,6 @@ + + + diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 993e794e7f..f442b976cc 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -302,7 +302,24 @@ export const settings: Record = { codeAreaLang: 'xml', storage: 'setting', ee_only: '' - } + }, + { + label: 'Maven/Ivy repositories', + description: 'Add private Maven/Ivy repositories', + key: 'maven_repos', + fieldType: 'text', + placeholder: 'https://user:password@artifacts.foo.com/maven', + storage: 'setting', + ee_only: '' + }, + { + label: 'Disable default Maven repository', + description: 'Do not use default Maven repository', + key: 'no_default_maven', + fieldType: 'boolean', + storage: 'setting', + ee_only: '' + }, ], Alerts: [ { diff --git a/frontend/src/lib/editorUtils.ts b/frontend/src/lib/editorUtils.ts index 12ebc7f0b1..2b8ddb6ba4 100644 --- a/frontend/src/lib/editorUtils.ts +++ b/frontend/src/lib/editorUtils.ts @@ -84,6 +84,9 @@ export function langToExt(lang: string): string { return 'cs' case 'nu': return 'nu' + case 'java': + return 'java' + // KJQXZ default: return 'unknown' } diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index 4c0e55fa94..3fa9c3053e 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -24,6 +24,7 @@ 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 initNuParser, { parse_nu } from 'windmill-parser-wasm-nu' +import initJavaParser, { parse_java } from 'windmill-parser-wasm-java' 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' @@ -34,6 +35,7 @@ import wasmUrlRust from 'windmill-parser-wasm-rust/windmill_parser_wasm_bg.wasm? 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 wasmUrlNu from 'windmill-parser-wasm-nu/windmill_parser_wasm_bg.wasm?url' +import wasmUrlJava from 'windmill-parser-wasm-java/windmill_parser_wasm_bg.wasm?url' import { workspaceStore } from './stores.js' import { argSigToJsonSchemaType } from './inferArgSig.js' @@ -71,6 +73,9 @@ async function initWasmCSharp() { async function initWasmNu() { await initNuParser(wasmUrlNu) } +async function initWasmJava() { + await initJavaParser(wasmUrlJava) +} export async function inferArgs( language: SupportedLanguage | 'bunnative' | undefined, @@ -186,6 +191,10 @@ export async function inferArgs( } else if (language == 'nu') { await initWasmNu() inferedSchema = JSON.parse(parse_nu(code)) + } else if (language == 'java') { + await initWasmJava() + inferedSchema = JSON.parse(parse_java(code)) + // KJQXZ } else { return null } diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index bdb5ce0870..1fad760e04 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -945,6 +945,61 @@ dependencies: content: "{{ my_result | to_json }}" dest: result.json ` +const JAVA_INIT_CODE = `//requirements: +//com.google.code.gson:gson:2.8.9 +//com.github.ricksbrown:cowsay:1.1.0 + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.github.ricksbrown.cowsay.Cowsay; +import com.github.ricksbrown.cowsay.plugin.CowExecutor; + +public class Main { + public static class Person { + private String name; + private int age; + + // Constructor + public Person(String name, int age) { + this.name = name; + this.age = age; + } + } + + public static Object main( + // Primitive + int a, + float b, + // Objects + Integer age, + Float d, + Object e, + String name, + // Lists + String[] f + // No trailing commas! + ){ + Gson gson = new Gson(); + + // Get resources + var theme = Wmill.getResource("f/app_themes/theme_0"); + System.out.println("Theme: " + theme); + + // Create a Person object + Person person = new Person( (name == "") ? "Alice" : name, (age == null) ? 30 : age); + + // Serialize the Person object to JSON + String json = gson.toJson(person); + System.out.println("Serialized JSON: " + json); + + // Use cowsay + String[] args = new String[]{"-f", "dragon", json }; + String result = Cowsay.say(args); + return result; + } +} +` +// KJQXZ export const INITIAL_CODE = { bun: { scriptInitCodeBlock: BUN_INIT_BLOCK, @@ -1028,7 +1083,11 @@ export const INITIAL_CODE = { }, bunnative: { script: BUNNATIVE_INIT_CODE - } + }, + java: { + script: JAVA_INIT_CODE + }, + // KJQXZ } export function isInitialCode(content: string): boolean { @@ -1132,6 +1191,9 @@ export function initialCode( return INITIAL_CODE.csharp.script } else if (language == 'nu') { return INITIAL_CODE.nu.script + } else if (language == 'java') { + return INITIAL_CODE.java.script + // KJQXZ } else if (language == 'bun' || language == 'bunnative') { if (kind == 'trigger') { return INITIAL_CODE.bun.trigger diff --git a/frontend/src/lib/scripts.ts b/frontend/src/lib/scripts.ts index 95c0869e36..8c66851674 100644 --- a/frontend/src/lib/scripts.ts +++ b/frontend/src/lib/scripts.ts @@ -45,6 +45,9 @@ export function scriptLangToEditorLang( return 'csharp' } else if (lang == 'nu') { return 'nu' + } else if (lang == 'java') { + return 'java' + // KJQXZ } else if (lang == undefined) { return 'typescript' } else { @@ -125,7 +128,9 @@ const scriptLanguagesArray: [SupportedLanguage | 'docker' | 'bunnative', string] ['ansible', 'Ansible Playbook'], ['csharp', 'C#'], ['docker', 'Docker'], - ['nu', 'Nu'] + ['nu', 'Nu'], + ['java', 'Java'] + // KJQXZ ] export function processLangs(selected: string | undefined, langs: string[]): string[] { if (selected === 'nativets') { @@ -134,7 +139,8 @@ export function processLangs(selected: string | undefined, langs: string[]): str let ls = langs.filter((lang) => lang !== 'nativets') //those languages are newer and may not be in the saved list - let nl = ['bunnative', 'rust', 'ansible', 'csharp', 'nu'] + let nl = ['bunnative', 'rust', 'ansible', 'csharp', 'nu', 'java'] + // KJQXZ nl.forEach((lang) => { if (!ls.includes(lang)) { ls.push(lang) diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index b68e25a77b..ad7c93f53a 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -755,7 +755,8 @@ priority: {job.priority} {/if} - {#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'oracledb', 'nativets', 'bash', 'powershell', 'php', 'rust', 'other', 'ansible', 'csharp', 'dependency', 'nu'].includes(job.tag)} + {#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'oracledb', 'nativets', 'bash', 'powershell', 'php', 'rust', 'other', 'ansible', 'csharp', 'nu', 'java', 'dependency'].includes(job.tag)} +
Tag: {job.tag}