Merge branch 'main' into claude/issue-6832-20251015-1048

This commit is contained in:
Ruben Fiszel
2025-10-17 07:25:38 +00:00
committed by GitHub
173 changed files with 15806 additions and 2705 deletions
+2 -3
View File
@@ -69,11 +69,10 @@ jobs:
./substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: cargo test
timeout-minutes: 16
run:
deno --version && bun -v && go version && python3 --version &&
run: deno --version && bun -v && go version && python3 --version &&
SQLX_OFFLINE=true
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
DISABLE_EMBEDDING=true RUST_LOG=info
DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go)
UV_PATH=$(which uv) cargo test --features
enterprise,deno_core,license,python,rust,scoped_cache,private --all --
+8
View File
@@ -0,0 +1,8 @@
{
"mcpServers": {
"svelte": {
"type": "http",
"url": "https://mcp.svelte.dev/mcp"
}
}
}
+46
View File
@@ -1,5 +1,51 @@
# Changelog
## [1.562.0](https://github.com/windmill-labs/windmill/compare/v1.561.0...v1.562.0) (2025-10-16)
### Features
* add support for sage intacct oauth ([#6794](https://github.com/windmill-labs/windmill/issues/6794)) ([c86b344](https://github.com/windmill-labs/windmill/commit/c86b3448b86e008f14a25280285cc2f498eb926a))
* dependency job debouncing ([#6769](https://github.com/windmill-labs/windmill/issues/6769)) ([defb6c9](https://github.com/windmill-labs/windmill/commit/defb6c9694ac294dbf19ba5cd42ce7399ad1b9ac))
### Bug Fixes
* add configurable timeout sse stream ([f723a1f](https://github.com/windmill-labs/windmill/commit/f723a1fb7227ae45661fea5cf2e6f9928a39672b))
## [1.561.0](https://github.com/windmill-labs/windmill/compare/v1.560.0...v1.561.0) (2025-10-16)
### Features
* ansible playbook execution git repo mode (repo viewer + UI utils) ([#6831](https://github.com/windmill-labs/windmill/issues/6831)) ([32fae7a](https://github.com/windmill-labs/windmill/commit/32fae7a10c769473c708970e18c1f8268d62183f))
### Bug Fixes
* **backend:** revert flow node opti for ai agents ([#6840](https://github.com/windmill-labs/windmill/issues/6840)) ([3b5c962](https://github.com/windmill-labs/windmill/commit/3b5c96247350b70fe947d204e9bff61f81be219c))
* fix job loader in public apps with jwt token ([a238750](https://github.com/windmill-labs/windmill/commit/a2387505544a04675a7c9fddf2ed8c042f8bfa42))
## [1.560.0](https://github.com/windmill-labs/windmill/compare/v1.559.0...v1.560.0) (2025-10-15)
### Features
* add support for zoho oauth ([#6809](https://github.com/windmill-labs/windmill/issues/6809)) ([9d9c29f](https://github.com/windmill-labs/windmill/commit/9d9c29fdfa15cc655854ec909dea944d10ce7374))
* **backend:** use flow nodes opti for ai agent steps ([#6808](https://github.com/windmill-labs/windmill/issues/6808)) ([8d5acda](https://github.com/windmill-labs/windmill/commit/8d5acda340cd105c5b0dfc2bfe59b7e996bd2707))
* build pydoc for wmill python client and mount in container image ([#6828](https://github.com/windmill-labs/windmill/issues/6828)) ([d75e9e3](https://github.com/windmill-labs/windmill/commit/d75e9e3d92d43f449a6296b367018f8fa3da6507))
* **settings:** add unsaved changes warning for workspace settings ([#6813](https://github.com/windmill-labs/windmill/issues/6813)) ([cb88187](https://github.com/windmill-labs/windmill/commit/cb8818796ddd68d2b2ee1dea5f9b0a648f0c1ec9))
### Bug Fixes
* always create instance groups with uuid ([#6826](https://github.com/windmill-labs/windmill/issues/6826)) ([48acc57](https://github.com/windmill-labs/windmill/commit/48acc57823792c9e795f9735712e1b2ed6d2b4e2))
* bug for loop flow inconsistent state ([#6815](https://github.com/windmill-labs/windmill/issues/6815)) ([2565222](https://github.com/windmill-labs/windmill/commit/256522273ee65b67075ac91408825b1c6e91ef06))
* fix concurrency key filter ([892ce64](https://github.com/windmill-labs/windmill/commit/892ce64ea8550c22d65180c71f57c90a65583832))
* gcp script picker ([#6837](https://github.com/windmill-labs/windmill/issues/6837)) ([d12c8f3](https://github.com/windmill-labs/windmill/commit/d12c8f34efe5ebbdbbf85ae41bb11307dc5d8ea3))
* resource editor should not autoselect resources for optional fields ([#6821](https://github.com/windmill-labs/windmill/issues/6821)) ([85d1b8a](https://github.com/windmill-labs/windmill/commit/85d1b8a3e6af41bba93128ebcb88ada383ed2d65))
* support dyn select for sub flow ([#6835](https://github.com/windmill-labs/windmill/issues/6835)) ([b211155](https://github.com/windmill-labs/windmill/commit/b211155784135b1377975a2759f2ddca1cffcea2))
## [1.559.0](https://github.com/windmill-labs/windmill/compare/v1.558.1...v1.559.0) (2025-10-14)
+1
View File
@@ -48,6 +48,7 @@ COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.
RUN cd /backend/windmill-api && . ./build_openapi.sh
COPY /backend/parsers/windmill-parser-wasm/pkg/ /backend/parsers/windmill-parser-wasm/pkg/
COPY /typescript-client/docs/ /frontend/static/tsdocs/
COPY /python-client/docs/ /frontend/static/pydocs/
RUN npm run generate-backend-client
ENV NODE_OPTIONS "--max-old-space-size=8192"
@@ -46,11 +46,11 @@
]
},
"nullable": [
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
true,
true
]
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL\n DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0"
"hash": "07335b75233811352fb898cf3d6c8fe7fd014adbf40cc4bc8c041f5864423367"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
"query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681"
"hash": "08c1121171b98889f188ea6b33b1861f3483fa70b5d58dd2838a5cb6dabe9cc1"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, edited_at = now()",
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = EXCLUDED.value, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "8bd028c8b5f8a4d566f89eebc2e63fd04beaf2b0b49e07c7df42ecddd70737f3"
"hash": "19e4625de06b8bab10039280a6213df5f38fb8892226f04cf700f60eb45199ef"
}
@@ -59,9 +59,7 @@
"failure",
"command",
"approval",
"preprocessor",
"schedule_handler_old",
"dynamic_skip"
"preprocessor"
]
}
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value",
"describe": {
"columns": [],
"parameters": {
@@ -15,5 +15,5 @@
},
"nullable": []
},
"hash": "9f1f388924176dbe3dea882e0c62728a82ba256029096812dd705ccb1a552cfe"
"hash": "1e426c8a06d7bbed7af67a105f74b3e03bd44048af4d69fab854f97fa821649b"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2",
"query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "a00e61e770e20157bbd9e4cdedf7fb5f9de7c8c9e50282e3ecf2e3ce917ec37a"
"hash": "24f38f0642b49626c8c8417e1846ab38dfe15284a6a7f54366ba25d7eb75a74a"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = $2",
"query": "INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = EXCLUDED.config",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "af00c212f509076e37538be52f582ba09e47db50ba93af322649ccddbb05cc49"
"hash": "25bf02e605e9e8e708a5dcfbb898b1af55045c1b8c2a138ca995882dde955971"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value",
"describe": {
"columns": [],
"parameters": {
@@ -16,5 +16,5 @@
},
"nullable": []
},
"hash": "55a2f170823f1d1abce76287d8817a6cf34de92b9b4079c00b75423a9ff835b9"
"hash": "2b34ae324f90924dba6c4562024f72e7b051adb368b3685181047f1f4522a473"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29"
"hash": "2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO instance_group (name, summary) VALUES ($1, $2) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "383b8adaadc6e23952ca8942fe7dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a"
"hash": "384753e7ceb602790646b0f269df48910d900b9575d986143823781e1d005d57"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, edited_at = now()",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -15,5 +15,5 @@
},
"nullable": []
},
"hash": "42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4"
"hash": "3904d59575e05df6e414be03e84ca7d07ee06d417a5bd9f02d64fcb467cf362a"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()",
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -15,5 +15,5 @@
},
"nullable": []
},
"hash": "c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e"
"hash": "39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2",
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1"
"hash": "4d3b8726656ebf6eb78d0c4c23fdfa6325fcae4dc3bf559f2ecc683fb40537e8"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text)",
"query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs)",
"describe": {
"columns": [],
"parameters": {
@@ -12,5 +12,5 @@
},
"nullable": []
},
"hash": "c7dd35561e9b1cfd86238d410139f50e4d87c762e4867d8b32c11bd8c74846eb"
"hash": "5585bc46fe2b9d5aebddad300f43b7cbc891e55d29d30d1ca7b13f25ed1fdc87"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, expires_at = $7",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at",
"describe": {
"columns": [],
"parameters": {
@@ -16,5 +16,5 @@
},
"nullable": []
},
"hash": "08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324"
"hash": "5701ee0b862dbdb44990702af270a2eb517e82943bb8c078c7bb2e60def3cbf1"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO instance_group (name, summary, id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "59c63c5e3ce0b4976133cd65258ef6bfdecc81700b89962c758c065d8d55f9e2"
}
@@ -1,17 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, $4, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "b5fbd7893950610f1285662df24f438c9855ba860e23befd88c2544ef86e9133"
"hash": "69550451b86f221a3d2ef626be7073fc77421710992b10f526aa36bc64ff0930"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO concurrency_locks (id, last_locked_at, owner)\n VALUES ($1, now(), $2)\n ON CONFLICT (id)\n DO UPDATE SET\n last_locked_at = now(),\n owner = $2",
"query": "INSERT INTO concurrency_locks (id, last_locked_at, owner)\n VALUES ($1, now(), $2)\n ON CONFLICT (id)\n DO UPDATE SET\n last_locked_at = now(),\n owner = EXCLUDED.owner",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d"
"hash": "6d7a4185063dbcca0dbea1b002330d622c9d2d844a2ab3938e6ec23c6150fb40"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT key FROM debounce_key WHERE job_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "key",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false
]
},
"hash": "73fcf81d272c1613e094d60c0a088f9d694bc37caacef7269e3738de8b5f6013"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM debounce_key WHERE key = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "76774e6f72c8c8b7473487e4176dc17b17372b7292e39d3888a93ff4fe49e4f5"
}
@@ -1,11 +1,11 @@
{
"db_name": "PostgreSQL",
"query": "SELECT versions[array_upper(versions, 1)] FROM flow WHERE path = $1 AND workspace_id = $2",
"query": "SELECT id FROM flow_version WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "versions",
"name": "id",
"type_info": "Int8"
}
],
@@ -16,8 +16,8 @@
]
},
"nullable": [
null
false
]
},
"hash": "526bfaccaafbe2e6f70dd5e6cd21c0c60d4ec155f79d067a8b74cf24eebad88c"
"hash": "7bfb3b210d23f2c00a1d6a653e9df5d7df9acf74de6dcc566924de02f1807af2"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin, operator)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, email)\n DO UPDATE SET is_admin = $3, operator = $4",
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin, operator)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, email)\n DO UPDATE SET is_admin = EXCLUDED.is_admin, operator = EXCLUDED.operator",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "d970a0b07a2b5840d1feb1baacb834dbaf91c633d3e7e1e29c8eb7eedc53e888"
"hash": "7dc75cb67922e31ffe0f88b03a8a5bc14039aff6f023d79139b20519a9cdbe7d"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, slack_name = $3, slack_email = $4",
"query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = EXCLUDED.slack_team_id, slack_name = EXCLUDED.slack_name, slack_email = EXCLUDED.slack_email",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "43b376a2eff086a32cd76e54361ce3631feee1565935d2a6ddbecc17950758d1"
"hash": "7dcc77eb6da5863f7a25ab6ad83d270e5e8a52540da1726aa875fcbe2517f16a"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_env (workspace_id, name, value) VALUES ($1, $2, $3) ON CONFLICT (workspace_id, name) DO UPDATE SET value = $3",
"query": "INSERT INTO workspace_env (workspace_id, name, value) VALUES ($1, $2, $3) ON CONFLICT (workspace_id, name) DO UPDATE SET value = EXCLUDED.value",
"describe": {
"columns": [],
"parameters": {
@@ -12,5 +12,5 @@
},
"nullable": []
},
"hash": "b8c66d905a6c7ffa6441c84b14ea897040069dac7367895813cc2d64a9867193"
"hash": "7dcf840fc5b329f4a591a51e24c9dacb12266f606b5ce7e8ed6110f3f381f945"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO debounce_stale_data (job_id, to_relock)\n VALUES ($1, $2)\n ON CONFLICT (job_id)\n DO UPDATE SET to_relock = (\n SELECT array_agg(DISTINCT x)\n FROM unnest(\n -- Combine existing array with new values, removing duplicates\n array_cat(debounce_stale_data.to_relock, EXCLUDED.to_relock)\n ) AS x\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"TextArray"
]
},
"nullable": []
},
"hash": "7ec724b84479c2f737637e91b8cbed6cae29f361167deee879b8b683ad1bf684"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO debounce_key (key, job_id) VALUES ($1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Uuid"
]
},
"nullable": []
},
"hash": "7ed404f3a8b23f98fb7c15a26b14f9e3e416e1baf20bdb25c9a8ac9efc288931"
}
@@ -1,51 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO script (summary, description, dedicated_worker, content, workspace_id, path, hash, language, tag, created_by, lock) VALUES ('', '', true, $1, $2, $3, $4, $5, $6, $7, '') ON CONFLICT (workspace_id, hash) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Varchar",
"Varchar",
"Int8",
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
},
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf"
}
@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632",
"query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632 OR version=20251006143821",
"describe": {
"columns": [],
"parameters": {
@@ -8,5 +8,5 @@
},
"nullable": []
},
"hash": "618ec69c9c78f1c9e3539d2770392e3f783f29cd2cc58c0fc87d14ecef32b467"
"hash": "8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2",
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c"
"hash": "8fda0400ec5ba04a2a1469672bbb0af413027dbd39aa2c4182e3d219640d397e"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM app_version WHERE app_id = (SELECT id FROM app WHERE path = $1 AND workspace_id = $2) ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "99e11c04bcc436ec7a75f46365423c85a6e6490c2d9e1dc6ac39112d763f0f75"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = $1::bigint::bit(64)",
"query": "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = EXCLUDED.progress",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "e40f7e0b61567f948bfea0b6f50518564634885ccc2c0d30ccca79fc13bdcf07"
"hash": "99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + EXCLUDED.usage",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "f9e1334ecb17b313924587d96fc8b0310b14d75e85a558c8cb70f2761923d175"
"hash": "9aebf706529889dc044e0ef41da8db2b1061c22698393c2e42d94da11e697b8b"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray",
"Jsonb"
]
},
"nullable": []
},
"hash": "a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, edited_at = now()",
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value, edited_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "20fcdcd2674a52ee9bd8d1de518d6bce075f20bcd5d2328f183d8a59331f6bb1"
"hash": "a5bf005e0f7c9a86a136e049445de059481091ac173414418b8678f6beadf2ac"
}
@@ -1,89 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, 1)) RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Int8",
"Varchar",
{
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlestepflow",
"flowscript",
"flownode",
"appscript",
"aiagent"
]
}
}
},
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
},
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Jsonb"
]
},
"nullable": [
false
]
},
"hash": "ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338"
}
@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
true,
false
false,
true
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO draft\n (workspace_id, path, value, typ)\n VALUES ($1, $2, $3::text::json, $4)\n ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = $3::text::json",
"query": "INSERT INTO draft\n (workspace_id, path, value, typ)\n VALUES ($1, $2, $3::text::json, $4)\n ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = EXCLUDED.value",
"describe": {
"columns": [],
"parameters": {
@@ -24,5 +24,5 @@
},
"nullable": []
},
"hash": "366609f7e7fbd73ea807128b931eff2f1ab763fa630c8531f590fed2110c03d9"
"hash": "b474ae4401b3d4c95add2d3353eb66512801c20ad685e8ed544fe4b86601aaa8"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray"
]
},
"nullable": []
},
"hash": "b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
"describe": {
"columns": [],
"parameters": {
@@ -129,5 +129,5 @@
},
"nullable": []
},
"hash": "8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce"
"hash": "bf2163c542fb8c4e173167a8f333ef762fecf782424c5b61b89f32918b8d6971"
}
@@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
false
]
},
"hash": "bf252ea52aeb57664ad5054b39998a5e50bc98a8360387bd4158f9bc1289319f"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\nWITH rows_to_delete AS (\n SELECT concurrency_id\n FROM concurrency_counter\n WHERE job_uuids = '{}'::jsonb\n FOR UPDATE SKIP LOCKED\n)\nDELETE FROM concurrency_counter\nWHERE concurrency_id IN (SELECT concurrency_id FROM rows_to_delete) RETURNING concurrency_id",
"query": "\nWITH rows_to_delete AS (\n SELECT concurrency_id\n FROM concurrency_counter\n \n WHERE job_uuids = '{}'::jsonb\n FOR UPDATE SKIP LOCKED\n)\nDELETE FROM concurrency_counter\nWHERE concurrency_id IN (SELECT concurrency_id FROM rows_to_delete) RETURNING concurrency_id",
"describe": {
"columns": [
{
@@ -16,5 +16,5 @@
false
]
},
"hash": "9f4811fe735d401b62f4b7bf3db2b5cd13eb3364a8a8546007dec7ab528b1f9d"
"hash": "c6ef0acdf20bd71dd26de981fb49f178ba8a1b8c1e01e0fec1dfd6a54ea7a894"
}
@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT versions[array_upper(versions, 1)] FROM app WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "versions",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "ca15fe5d43f0e94f50408efe5c9e359770b759e8661687b4503c4b692ecd245e"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "afc7c23c057748f6d4a61dbef17e433b8875c6588b91e38e8141d12189118412"
"hash": "cc8e10a4f39e118b145cc8f6fbd5f3db1fb30c9564ed039ad4c952333ec29b39"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + EXCLUDED.usage",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "7b48820a08fbb3bee0fb2dc802a0b4f28ed5507797165e8c577ce6a95d11694c"
"hash": "ceb10ca124d9425b24d81d8a279a13481db39eea0b52cc513b3ee6571cf44ed4"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "to_relock",
"type_info": "TextArray"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
true
]
},
"hash": "cfe06702916362aaf5122bb95593eff389e0d44b7a58b69fd5c79629599902fc"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO job_result_stream_v2 (workspace_id, job_id, stream, idx)\n VALUES (\n $1, \n $2,\n $3, \n $4\n )\n ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || $3\n ",
"query": "\n INSERT INTO job_result_stream_v2 (workspace_id, job_id, stream, idx)\n VALUES (\n $1, \n $2,\n $3, \n $4\n )\n ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || EXCLUDED.stream\n ",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde"
"hash": "d142ff8b56f4b69c20815230b5b763ce8ce9fbe89abe83989f52ff3657690fef"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2",
"query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2 AND deleted = false ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
@@ -19,5 +19,5 @@
false
]
},
"hash": "16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f"
"hash": "d5661c7557cf3a8dee7cf799cd364d21d38edb827d2c08b0ca7d72311b78d574"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO global_settings (name, value) VALUES ('slack', $1) ON CONFLICT (name) DO UPDATE SET value = $1, updated_at = now()",
"query": "INSERT INTO global_settings (name, value) VALUES ('slack', $1) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()",
"describe": {
"columns": [],
"parameters": {
@@ -10,5 +10,5 @@
},
"nullable": []
},
"hash": "fb581eb6f883ebb5909a00c65a6e1217f088290fce0e052171230bcd88f945b9"
"hash": "da7e23a32f284c9760614735d459474d2f8513202dfb64767b30e7b50833b857"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO cloud_workspace_settings (workspace_id, threshold_alert_amount) VALUES ($1, $2) ON CONFLICT (workspace_id) DO UPDATE SET threshold_alert_amount = $2, last_alert_sent = NULL",
"query": "INSERT INTO cloud_workspace_settings (workspace_id, threshold_alert_amount) VALUES ($1, $2) ON CONFLICT (workspace_id) DO UPDATE SET threshold_alert_amount = EXCLUDED.threshold_alert_amount, last_alert_sent = NULL",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "c4e0f3eab227a798d9cd7478db50a7c1a588e69c2c8d9a1def9276ba326453d2"
"hash": "db201730803047bfabccf5f10456243b590b2dd0cfe41aa5100a7e829afad9d3"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text) RETURNING length(logs)",
"query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs) RETURNING length(logs)",
"describe": {
"columns": [
{
@@ -20,5 +20,5 @@
null
]
},
"hash": "a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c"
"hash": "e16f464c7e302e80f1017ff6550716448202c1181ca0b36936a9c8840c14a95d"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981"
"hash": "e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) DO UPDATE set ip = $3, custom_tags = $4, worker_group = $5",
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) \n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group",
"describe": {
"columns": [],
"parameters": {
@@ -18,5 +18,5 @@
},
"nullable": []
},
"hash": "6afc5c7cbb3abe11ade0cedf1f7328005ce4de3165cdd998e5a0d27e044c7153"
"hash": "e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d"
"hash": "e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69"
}
@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
false
]
},
"hash": "f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT job_id FROM debounce_key WHERE key = $1 FOR UPDATE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "job_id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "fe1539db7384c8edc6d8ec672495fe3964efb8551ad6d74f141fc457034cc5b9"
}
@@ -1,89 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Int8",
"Varchar",
{
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlestepflow",
"flowscript",
"flownode",
"appscript",
"aiagent"
]
}
}
},
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
},
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Int4"
]
},
"nullable": [
false
]
},
"hash": "ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238"
}
+67 -75
View File
@@ -122,7 +122,7 @@ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"const-random",
"getrandom 0.3.3",
"getrandom 0.3.4",
"once_cell",
"version_check",
"zerocopy",
@@ -791,9 +791,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
version = "1.14.0"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786"
checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -801,9 +801,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.31.0"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd"
checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c"
dependencies = [
"bindgen 0.72.1",
"cc",
@@ -1023,7 +1023,7 @@ dependencies = [
"pin-project-lite",
"rustls 0.21.12",
"rustls 0.23.29",
"rustls-native-certs 0.8.1",
"rustls-native-certs 0.8.2",
"rustls-pki-types",
"tokio",
"tower 0.5.2",
@@ -1962,9 +1962,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
@@ -4831,9 +4831,9 @@ dependencies = [
[[package]]
name = "dyn-stack-macros"
version = "0.1.0"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05dbec7076f432bb132db738df90d87a4f5789e99f59e7b1219a6b8ef61eaa68"
checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9"
[[package]]
name = "dynasm"
@@ -5870,21 +5870,21 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasi 0.14.7+wasi-0.2.4",
"wasip2",
"wasm-bindgen",
]
@@ -5943,9 +5943,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "globset"
version = "0.4.16"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
dependencies = [
"aho-corasick",
"bstr",
@@ -6666,7 +6666,7 @@ dependencies = [
"hyper-util",
"log",
"rustls 0.23.29",
"rustls-native-certs 0.8.1",
"rustls-native-certs 0.8.2",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.4",
@@ -6911,9 +6911,9 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
[[package]]
name = "ignore"
version = "0.4.23"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403"
dependencies = [
"crossbeam-deque",
"globset",
@@ -7182,7 +7182,7 @@ version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.4",
"libc",
]
@@ -8180,7 +8180,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
"windows-sys 0.48.0",
]
@@ -8191,7 +8191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
"windows-sys 0.59.0",
]
@@ -8330,7 +8330,7 @@ dependencies = [
"bytes",
"crc32fast",
"flate2",
"getrandom 0.3.3",
"getrandom 0.3.4",
"mysql-common-derive",
"num-bigint",
"num-traits",
@@ -9058,9 +9058,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.73"
version = "0.10.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654"
dependencies = [
"bitflags 2.9.4",
"cfg-if",
@@ -9099,9 +9099,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.109"
version = "0.9.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
dependencies = [
"cc",
"libc",
@@ -10179,9 +10179,9 @@ dependencies = [
[[package]]
name = "pure-rust-locales"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a"
checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d"
[[package]]
name = "pwd"
@@ -10252,7 +10252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
"bytes",
"getrandom 0.3.3",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.0",
"ring 0.17.14",
@@ -10368,7 +10368,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.4",
]
[[package]]
@@ -10700,7 +10700,7 @@ dependencies = [
"pin-project-lite",
"quinn",
"rustls 0.23.29",
"rustls-native-certs 0.8.1",
"rustls-native-certs 0.8.2",
"rustls-pki-types",
"serde",
"serde_json",
@@ -11161,9 +11161,9 @@ dependencies = [
[[package]]
name = "rustls-native-certs"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923"
dependencies = [
"openssl-probe",
"rustls-pki-types",
@@ -13305,7 +13305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.2",
"windows-sys 0.61.2",
@@ -13832,7 +13832,7 @@ dependencies = [
"httparse",
"rand 0.8.5",
"ring 0.17.14",
"rustls-native-certs 0.8.1",
"rustls-native-certs 0.8.2",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.4",
@@ -13925,7 +13925,7 @@ dependencies = [
"percent-encoding",
"pin-project",
"prost",
"rustls-native-certs 0.8.1",
"rustls-native-certs 0.8.2",
"rustls-pemfile 2.2.0",
"socket2 0.5.10",
"tokio",
@@ -14652,7 +14652,7 @@ version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.4",
"js-sys",
"serde",
"wasm-bindgen",
@@ -14760,15 +14760,6 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
@@ -15131,7 +15122,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"aws-sdk-config",
@@ -15191,7 +15182,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"argon2",
@@ -15311,7 +15302,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -15326,7 +15317,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"chrono",
"serde",
@@ -15339,7 +15330,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"axum",
@@ -15358,7 +15349,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15442,7 +15433,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"regex",
"serde",
@@ -15457,7 +15448,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"bytes",
@@ -15481,7 +15472,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -15493,7 +15484,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -15502,7 +15493,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15514,7 +15505,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15526,7 +15517,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"gosyn",
@@ -15538,7 +15529,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15550,7 +15541,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15562,7 +15553,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -15573,7 +15564,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15584,7 +15575,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15596,7 +15587,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15619,7 +15610,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15633,7 +15624,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -15650,7 +15641,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15664,7 +15655,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15682,7 +15673,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"getrandom 0.2.16",
@@ -15707,9 +15698,10 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"serde",
"serde_json",
"windmill-parser",
"yaml-rust",
@@ -15717,7 +15709,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15750,7 +15742,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -15760,7 +15752,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.559.0"
version = "1.562.0"
dependencies = [
"anyhow",
"async-once-cell",
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.559.0"
version = "1.562.0"
authors.workspace = true
edition.workspace = true
@@ -34,7 +34,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.559.0"
version = "1.562.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -86,6 +86,7 @@ oauth2 = ["windmill-api/oauth2"]
zip = ["windmill-api/zip"]
static_frontend = ["windmill-api/static_frontend"]
scoped_cache = ["windmill-common/scoped_cache"]
test_job_debouncing = []
# Languages
python = ["windmill-worker/python", "windmill-api/python"]
rust = ["windmill-worker/rust"]
+1 -1
View File
@@ -1 +1 @@
19909371503adfa12106dbf261d7f4134989a204
365a3c9dd7a3fddc3280bbda86ba93ef149d1b64
@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS debounce_key;
DROP TABLE IF EXISTS debounce_stale_data;
DROP TABLE IF EXISTS debounce_obj_latest_version;
@@ -0,0 +1,19 @@
CREATE TABLE debounce_key (
key VARCHAR(255) NOT NULL,
job_id uuid NOT NULL,
PRIMARY KEY (key)
);
CREATE TABLE debounce_stale_data (
job_id uuid NOT NULL,
to_relock TEXT[],
PRIMARY KEY (job_id)
);
-- TODO: Prune on move/deletion
-- But normally this will persist across runs.
-- CREATE TABLE unlocked_script_latest_version (
-- key VARCHAR(255) NOT NULL,
-- version BIGINT NOT NULL,
-- PRIMARY KEY (key)
-- );
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,37 @@
-- Users of instance_settings.yaml would have issues where it deletes ducklake_user_pg_pwd
-- and then the next migration fails because it tries to insert a NULL value
-- When everything is fine (i.e ducklake_user_pg_pwd or ducklake_settings is present)
-- this should be a no-op
DO $$
DECLARE
new_settings_value text;
old_setting_value text;
BEGIN
SELECT value INTO new_settings_value FROM global_settings WHERE name = 'ducklake_settings';
SELECT trim(both '"' from value::text) INTO old_setting_value FROM global_settings WHERE name = 'ducklake_user_pg_pwd';
IF new_settings_value IS NULL AND old_setting_value IS NULL THEN
-- Copied from 20250731132157_ducklake_instance_settings.up.sql
INSERT INTO global_settings (name, value)
VALUES ('ducklake_user_pg_pwd', ('"' || gen_random_uuid()::text || '"')::jsonb)
ON CONFLICT DO NOTHING;
-- Cannot simply create the user because Postgres expect a static string for the password
-- Also we cannot drop the user easily in the down migration because databases will depend on it
-- And we cannot drop databases in transactions (migrations)
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ducklake_user') THEN
EXECUTE format('CREATE USER ducklake_user WITH PASSWORD %L', old_setting_value);
ELSE
EXECUTE format('ALTER USER ducklake_user WITH PASSWORD %L', old_setting_value);
END IF;
END IF;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'ducklake_user migration error, skipping.';
END
$$;
@@ -0,0 +1,4 @@
-- Add down migration script here
-- This migration is irreversible as we cannot safely remove UUIDs
-- that may already be in use by SCIM clients
@@ -0,0 +1,7 @@
-- Add up migration script here
-- Backfill UUIDs for instance groups that don't have one
-- This is needed for SCIM compatibility where groups must have stable UUIDs
UPDATE instance_group
SET id = gen_random_uuid()::text
WHERE id IS NULL;
+19 -48
View File
@@ -2,47 +2,32 @@
"github": {
"auth_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"scopes": [
"workflow",
"repo"
]
"scopes": ["workflow", "repo"]
},
"gitlab": {
"auth_url": "https://gitlab.com/oauth/authorize",
"token_url": "https://gitlab.com/oauth/token",
"scopes": [
"api"
]
"scopes": ["api"]
},
"bitbucket": {
"auth_url": "https://bitbucket.org/site/oauth2/authorize",
"token_url": "https://bitbucket.org/site/oauth2/access_token",
"scopes": [
"repository"
]
"scopes": ["repository"]
},
"slack": {
"auth_url": "https://slack.com/oauth/authorize",
"token_url": "https://slack.com/api/oauth.access",
"scopes": [
"chat:write:user",
"users:read",
"users:read.email"
]
"scopes": ["chat:write:user", "users:read", "users:read.email"]
},
"supabase_wizard": {
"auth_url": "https://api.supabase.com/v1/oauth/authorize",
"token_url": "https://api.supabase.com/v1/oauth/token",
"scopes": [
"all"
]
"scopes": ["all"]
},
"gsheets": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"scopes": ["https://www.googleapis.com/auth/spreadsheets"],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
@@ -51,9 +36,7 @@
"gdrive": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/drive"
],
"scopes": ["https://www.googleapis.com/auth/drive"],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
@@ -62,9 +45,7 @@
"gmail": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/gmail.send"
],
"scopes": ["https://www.googleapis.com/auth/gmail.send"],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
@@ -73,9 +54,7 @@
"gcal": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/calendar.events"
],
"scopes": ["https://www.googleapis.com/auth/calendar.events"],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
@@ -84,9 +63,7 @@
"gforms": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/forms"
],
"scopes": ["https://www.googleapis.com/auth/forms"],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
@@ -95,9 +72,7 @@
"gcloud": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/cloud-platform"
],
"scopes": ["https://www.googleapis.com/auth/cloud-platform"],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
@@ -128,19 +103,13 @@
"linkedin": {
"auth_url": "https://www.linkedin.com/oauth/v2/authorization",
"token_url": "https://www.linkedin.com/oauth/v2/accessToken",
"scopes": [
"w_member_social",
"r_liteprofile",
"r_emailaddress"
],
"scopes": ["w_member_social", "r_liteprofile", "r_emailaddress"],
"req_body_auth": true
},
"quickbooks": {
"auth_url": "https://appcenter.intuit.com/connect/oauth2",
"token_url": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
"scopes": [
"com.intuit.quickbooks.accounting"
]
"scopes": ["com.intuit.quickbooks.accounting"]
},
"visma": {
"auth_url": "https://connect.visma.com/connect/authorize",
@@ -153,6 +122,11 @@
"vismanet_erp_interactive_api:update"
]
},
"sage_intacct": {
"auth_url": "https://api.intacct.com/ia/api/v1/oauth2/authorize",
"token_url": "https://api.intacct.com/ia/api/v1/oauth2/token",
"scopes": ["offline_access"]
},
"spotify": {
"auth_url": "https://accounts.spotify.com/authorize",
"token_url": "https://accounts.spotify.com/api/token",
@@ -175,10 +149,7 @@
"xero": {
"auth_url": "https://login.xero.com/identity/connect/authorize",
"token_url": "https://identity.xero.com/connect/token",
"scopes": [
"offline_access",
"accounting.transactions"
]
"scopes": ["offline_access", "accounting.transactions"]
},
"zoho": {
"auth_url": "https://accounts.zoho.com/oauth/v2/auth",
@@ -150,6 +150,16 @@ pub fn parse_ansible(code: &str) -> String {
wrap_sig(windmill_parser_yaml::parse_ansible_sig(code))
}
#[cfg(feature = "ansible-parser")]
#[wasm_bindgen]
pub fn parse_ansible_delegate(code: &str) -> String {
if let Ok(r) = windmill_parser_yaml::parse_delegate_to_git_repo(code) {
return serde_json::to_string(&r).unwrap();
} else {
return "Invalid".to_string();
}
}
#[cfg(feature = "csharp-parser")]
#[wasm_bindgen]
pub fn parse_csharp(code: &str) -> String {
@@ -173,6 +183,7 @@ pub fn parse_java(code: &str) -> String {
pub fn parse_ruby(code: &str) -> String {
wrap_sig(windmill_parser_ruby::parse_ruby_signature(code))
}
#[cfg(feature = "sql-parser")]
#[wasm_bindgen]
pub fn parse_assets_sql(code: &str) -> String {
@@ -203,4 +214,15 @@ pub fn parse_assets_py(code: &str) -> String {
}
}
#[cfg(feature = "ansible-parser")]
#[wasm_bindgen]
pub fn parse_assets_ansible(code: &str) -> String {
let o = windmill_parser_yaml::parse_assets(code);
if let Ok(r) = o {
return serde_json::to_string(&r).unwrap();
} else {
return format!("err: {:?}", o.err().unwrap());
}
}
// for related places search: ADD_NEW_LANG
@@ -13,3 +13,4 @@ yaml-rust.workspace = true
windmill-parser.workspace = true
anyhow.workspace = true
serde_json.workspace = true
serde.workspace = true
@@ -0,0 +1,41 @@
use windmill_parser::asset_parser::{
merge_assets, AssetKind, AssetUsageAccessType, ParseAssetsResult,
};
use crate::{parse_ansible_reqs, ResourceOrVariablePath};
pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult<String>>> {
let mut assets = vec![];
if let (_, Some(ansible_reqs), _) = parse_ansible_reqs(input)? {
if let Some(delegate_to_git_repo_details) = ansible_reqs.delegate_to_git_repo {
assets.push(ParseAssetsResult {
kind: AssetKind::Resource,
path: delegate_to_git_repo_details.resource,
access_type: Some(AssetUsageAccessType::R),
})
}
for i in ansible_reqs.inventories {
if let Some(pinned_res) = i.pinned_resource {
assets.push(ParseAssetsResult {
kind: AssetKind::Resource,
path: pinned_res,
access_type: Some(AssetUsageAccessType::R),
})
}
}
for file in ansible_reqs.file_resources {
if let ResourceOrVariablePath::Resource(resource) = file.resource_path {
assets.push(ParseAssetsResult {
kind: AssetKind::Resource,
path: resource,
access_type: Some(AssetUsageAccessType::R),
})
}
}
}
Ok(merge_assets(assets))
}
+284 -22
View File
@@ -1,15 +1,26 @@
use std::collections::HashMap;
use anyhow::anyhow;
use serde::Serialize;
use serde_json::json;
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ};
use yaml_rust::{Yaml, YamlEmitter, YamlLoader};
pub mod asset_parser;
pub use asset_parser::parse_assets;
pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result<MainArgSignature> {
let docs = YamlLoader::load_from_str(inner_content)
.map_err(|e| anyhow!("Failed to parse yaml: {}", e))?;
if docs.len() < 2 {
let mut delegating_to_git_repo = false;
if let Yaml::Hash(doc) = &docs[0] {
if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) {
delegating_to_git_repo = extract_delegate_to_git_repo_details(v).is_some();
}
}
if docs.len() < 2 && !delegating_to_git_repo {
return Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
@@ -56,7 +67,21 @@ pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result<MainArgSignature
has_default: inv.default.is_some(),
default: inv.default.map(|v| json!(format!("$res:{}", v))),
oidx: None,
})
});
}
}
Yaml::String(key) if key == "additional_inventories" => {
for inv in parse_additional_inventories(value)? {
if let PreexistingAnsibleInventory::PassedInArgs(i) = inv {
args.push(Arg {
name: i.name,
otyp: None,
typ: Typ::List(Box::new(Typ::Str(i.options))),
has_default: false,
default: None,
oidx: None,
});
}
}
}
_ => (),
@@ -211,6 +236,18 @@ pub struct AnsibleInventory {
pub pinned_resource: Option<String>,
}
#[derive(Debug, Clone)]
pub enum PreexistingAnsibleInventory {
Static(String),
PassedInArgs(InventoryFilenameListDefinition),
}
#[derive(Debug, Clone)]
pub struct InventoryFilenameListDefinition {
pub options: Option<Vec<String>>,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct GitRepo {
pub url: String,
@@ -219,12 +256,22 @@ pub struct GitRepo {
pub target_path: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct DelegateToGitRepoDetails {
pub resource: String,
pub playbook: Option<String>,
pub commit: Option<String>,
pub inventories_location: Option<String>,
pub vars_location: Option<String>,
}
#[derive(Debug, Clone)]
pub struct AnsibleRequirements {
pub python_reqs: Vec<String>,
pub roles_and_collections: Option<String>,
pub file_resources: Vec<FileResource>,
pub inventories: Vec<AnsibleInventory>,
pub additional_inventories: Vec<PreexistingAnsibleInventory>,
pub vars: Vec<(String, String)>,
pub resources: Vec<(String, String)>,
pub options: AnsiblePlaybookOptions,
@@ -232,6 +279,7 @@ pub struct AnsibleRequirements {
pub vault_id: Vec<String>,
pub git_repos: Vec<GitRepo>,
pub git_ssh_identity: Vec<String>,
pub delegate_to_git_repo: Option<DelegateToGitRepoDetails>,
}
impl Default for AnsibleRequirements {
@@ -241,6 +289,7 @@ impl Default for AnsibleRequirements {
roles_and_collections: None,
file_resources: vec![],
inventories: vec![],
additional_inventories: vec![],
vars: vec![],
resources: vec![],
options: AnsiblePlaybookOptions {
@@ -254,10 +303,57 @@ impl Default for AnsibleRequirements {
vault_id: vec![],
git_repos: vec![],
git_ssh_identity: vec![],
delegate_to_git_repo: None,
}
}
}
fn parse_additional_inventories(
inventory_yaml: &Yaml,
) -> anyhow::Result<Vec<PreexistingAnsibleInventory>> {
if let Yaml::Array(arr) = inventory_yaml {
let mut ret = vec![];
let mut count = -1;
for inv in arr {
if let Yaml::String(inv_name) = inv {
ret.push(PreexistingAnsibleInventory::Static(inv_name.clone()));
} else if let Yaml::Hash(inv) = inv {
if let Some(options) = inv.get(&Yaml::String("options".to_string())) {
let options = match options {
Yaml::Null => None,
Yaml::Array(elements) => Some(
elements
.iter()
.filter_map(|s| s.as_str().map(|s| s.to_string()))
.collect(),
),
_ => continue,
};
let name = inv
.get(&Yaml::String("name".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| {
count += 1;
if count == 0 {
"Additional inventories".to_string()
} else {
format!("Additional inventories ({count})")
}
});
ret.push(PreexistingAnsibleInventory::PassedInArgs(
InventoryFilenameListDefinition { options, name },
))
}
}
}
return Ok(ret);
}
return Err(anyhow!("Invalid inventory definition"));
}
fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result<Vec<AnsibleInventory>> {
if let Yaml::Array(arr) = inventory_yaml {
let mut ret = vec![];
@@ -303,6 +399,33 @@ fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result<Vec<AnsibleInvento
return Err(anyhow!("Invalid inventory definition"));
}
#[derive(Debug, Clone, Serialize)]
pub struct DelegateWithSSHAuth {
delegate_to_git_repo_details: Option<DelegateToGitRepoDetails>,
git_ssh_identity: Vec<String>,
}
pub fn parse_delegate_to_git_repo(inner_content: &str) -> anyhow::Result<DelegateWithSSHAuth> {
let docs = YamlLoader::load_from_str(inner_content)
.map_err(|e| anyhow!("Failed to parse yaml: {}", e))?;
let mut git_ssh_identity: Vec<String> = vec![];
if let Yaml::Hash(doc) = &docs[0] {
if let Some(v) = doc.get(&Yaml::String("git_ssh_identity".to_string())) {
let _ = extract_ssh_identity(&v, &mut git_ssh_identity);
}
if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) {
return Ok(DelegateWithSSHAuth {
delegate_to_git_repo_details: extract_delegate_to_git_repo_details(v),
git_ssh_identity,
});
}
}
Ok(DelegateWithSSHAuth { delegate_to_git_repo_details: None, git_ssh_identity })
}
pub fn parse_ansible_reqs(
inner_content: &str,
) -> anyhow::Result<(String, Option<AnsibleRequirements>, String)> {
@@ -310,11 +433,17 @@ pub fn parse_ansible_reqs(
let docs = YamlLoader::load_from_str(inner_content)
.map_err(|e| anyhow!("Failed to parse yaml: {}", e))?;
if docs.len() < 2 {
return Ok((logs, None, inner_content.to_string()));
let mut ret = AnsibleRequirements::default();
if let Yaml::Hash(doc) = &docs[0] {
if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) {
ret.delegate_to_git_repo = extract_delegate_to_git_repo_details(v);
}
}
let mut ret = AnsibleRequirements::default();
if ret.delegate_to_git_repo.is_none() && docs.len() < 2 {
return Ok((logs, None, inner_content.to_string()));
}
if let Yaml::Hash(doc) = &docs[0] {
for (key, value) in doc {
@@ -367,7 +496,11 @@ pub fn parse_ansible_reqs(
}
}
Yaml::String(key) if key == "inventory" => {
ret.inventories = parse_inventories(value)?;
ret.inventories.extend(parse_inventories(value)?);
}
Yaml::String(key) if key == "additional_inventories" => {
ret.additional_inventories
.extend(parse_additional_inventories(value)?);
}
Yaml::String(key) if key == "vault_password" => {
let Yaml::String(filename) = value else {
@@ -407,27 +540,15 @@ pub fn parse_ansible_reqs(
}
}
Yaml::String(key) if key == "git_ssh_identity" => {
let Yaml::Array(indentities) = &value else {
return Err(anyhow!(
"git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs"
));
};
for r in indentities {
let Yaml::String(file_name) = r else {
return Err(anyhow!(
"Git ssh identity file must be a string path to a Windmill variable/secret"
));
};
ret.git_ssh_identity.push(file_name.clone());
}
extract_ssh_identity(&value, &mut ret.git_ssh_identity)?;
}
Yaml::String(key) if key == "delegate_to_git_repo" => {} // Skip this because it was already parsed before
Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)),
_ => (),
}
}
}
let mut out_str = String::new();
let mut emitter = YamlEmitter::new(&mut out_str);
@@ -437,6 +558,61 @@ pub fn parse_ansible_reqs(
Ok((logs, Some(ret), out_str))
}
fn extract_ssh_identity(value: &Yaml, ret: &mut Vec<String>) -> anyhow::Result<()> {
let Yaml::Array(indentities) = value else {
return Err(anyhow!(
"git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs"
));
};
for r in indentities {
let Yaml::String(file_name) = r else {
return Err(anyhow!(
"Git ssh identity file must be a string path to a Windmill variable/secret"
));
};
ret.push(file_name.clone());
}
Ok(())
}
fn extract_delegate_to_git_repo_details(value: &Yaml) -> Option<DelegateToGitRepoDetails> {
if let Yaml::Hash(v) = value {
if let Some(resource) = v
.get(&Yaml::String("resource".to_string()))
.and_then(|s| s.as_str())
.map(|s| s.to_string())
{
let playbook = v
.get(&Yaml::String("playbook".to_string()))
.and_then(|s| s.as_str())
.map(|s| s.to_string());
let commit = v
.get(&Yaml::String("commit".to_string()))
.and_then(|s| s.as_str())
.map(|s| s.to_string());
let inventories_location = v
.get(&Yaml::String("inventories_location".to_string()))
.and_then(|s| s.as_str())
.map(|s| s.to_string());
let vars_location = v
.get(&Yaml::String("vars_location".to_string()))
.and_then(|s| s.as_str())
.map(|s| s.to_string());
return Some(DelegateToGitRepoDetails {
resource,
playbook,
commit,
inventories_location,
vars_location,
});
}
}
return None;
}
fn parse_git_repo(r: &Yaml) -> anyhow::Result<GitRepo> {
let Yaml::Hash(repo) = r else {
return Err(anyhow!("Should be a Map"));
@@ -679,7 +855,7 @@ pub fn add_versions_to_requirements_yaml(
input: &str,
role_versions: &HashMap<String, String>,
collection_versions: &HashMap<String, String>,
) -> anyhow::Result<(String,String)> {
) -> anyhow::Result<(String, String)> {
let mut docs =
YamlLoader::load_from_str(input).map_err(|e| anyhow!("YAML parse error: {}", e))?;
let doc = &mut docs[0];
@@ -709,3 +885,89 @@ pub fn add_versions_to_requirements_yaml(
Ok((out_str, logs))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_ansible_assets() {
let p = r#"
---
inventory:
- resource_type: ansible_inventory
# You can pin an inventory to this script by hardcoding the resource path:
# resource: u/user/your_resource
# - name: hcloud.yml
# resource_type: dynamic_inventory
additional_inventories:
- options: ["a", "b", "c"]
options:
- verbosity: vvv
delegate_to_git_repo:
resource: u/admin/git_reportino
playbook: ./playbooks/playbook.yml
commit: 7sh7dh73h7dhd299d91hd1hdh3d3hygh4372
# File resources will be written in the relative `target` location before
# running the playbook
files:
- resource: u/user/fabulous_jinja_template
target: ./config_template.j2
- variable: u/user/ssh_key
target: ./ssh_key
mode: '0600'
# Define the arguments of the windmill script
extra_vars:
world_qualifier:
type: string
# If using Ansible Vault:
# vault_password: u/user/ansible_vault_password
dependencies:
galaxy:
collections:
- name: community.general
- name: community.vmware
roles:
python:
- jmespath
---
- name: Echo
hosts: 127.0.0.1
connection: local
vars:
my_result:
a: 2
b: true
c: "Hello"
tasks:
- name: Print debug message
debug:
msg: "Hello, {{world_qualifier}} world!"
- name: Write variable my_result to result.json
delegate_to: localhost
copy:
content: "{{ my_result | to_json }}"
dest: result.json
"#;
let a = parse_assets(p).unwrap();
println!("The resulting assets are: {}", a.len());
let a = parse_ansible_reqs(p).unwrap();
println!("The resulting reqs are: {:#?}", a);
let a = parse_ansible_sig(p).unwrap();
println!("The resulting sig is: {:#?}", a);
let a = parse_delegate_to_git_repo(p).unwrap();
println!("The resulting delegate_to_kit_repo is: {:#?}", a);
}
}
@@ -26,6 +26,13 @@ pub struct ParseAssetsResult<S: AsRef<str>> {
pub access_type: Option<AssetUsageAccessType>, // None in case of ambiguity
}
#[derive(Debug, Clone, Serialize)]
pub struct DelegateToGitRepoDetails {
pub resource: String,
pub playbook: Option<String>,
pub commit: Option<String>,
}
pub fn merge_assets<S: AsRef<str>>(assets: Vec<ParseAssetsResult<S>>) -> Vec<ParseAssetsResult<S>> {
let mut arr: Vec<ParseAssetsResult<S>> = vec![];
for asset in assets {
+3 -1
View File
@@ -1588,6 +1588,7 @@ pub async fn monitor_db(
}
}
};
// run every hour (60 minutes / 30 seconds = 120)
let cleanup_worker_group_stats_f = async {
if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(120) {
@@ -2415,6 +2416,7 @@ async fn cleanup_concurrency_counters_empty_keys(db: &DB) -> error::Result<()> {
WITH rows_to_delete AS (
SELECT concurrency_id
FROM concurrency_counter
WHERE job_uuids = '{}'::jsonb
FOR UPDATE SKIP LOCKED
)
@@ -2724,7 +2726,7 @@ pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<(
async fn generate_and_save_jwt_secret(db: &DB) -> error::Result<String> {
let secret = rd_string(32);
sqlx::query!(
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2",
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value",
JWT_SECRET_SETTING,
serde_json::to_value(&secret).unwrap()
).execute(db).await?;
+45 -15
View File
@@ -20,6 +20,17 @@ use windmill_common::{
};
use windmill_queue::PushIsolationLevel;
pub async fn init_client(db: Pool<Postgres>) -> (windmill_api_client::Client, u16, ApiServer) {
initialize_tracing().await;
let server = ApiServer::start(db).await.unwrap();
let port = server.addr.port();
let client = windmill_api_client::create_client(
&format!("http://localhost:{port}"),
"SECRET_TOKEN".to_string(),
);
(client, port, server)
}
/// it's important this is unique between tests as there is one prometheus registry and
/// run_worker shouldn't register the same metric with the same worker name more than once.
///
@@ -131,7 +142,7 @@ impl RunJob {
let tx = PushIsolationLevel::IsolatedRoot(db.clone());
let (uuid, tx) = windmill_queue::push(
&db,
db,
tx,
"test-workspace",
payload,
@@ -157,6 +168,7 @@ impl RunJob {
None,
false,
None,
None,
)
.await
.expect("push has to succeed");
@@ -170,8 +182,8 @@ impl RunJob {
let uuid = self.push(db).await;
let listener = listen_for_completed_jobs(db).await;
in_test_worker(db, listener.find(&uuid), port).await;
let r = completed_job(uuid, db).await;
r
completed_job(uuid, db).await
}
/// push the job, spawn a worker, wait until the job is in completed_job
@@ -185,8 +197,8 @@ impl RunJob {
let listener = listen_for_completed_jobs(db).await;
test(uuid).await;
in_test_worker(db, listener.find(&uuid), port).await;
let r = completed_job(uuid, db).await;
r
completed_job(uuid, db).await
}
}
@@ -251,10 +263,10 @@ pub fn spawn_test_worker(
let base_internal_url = format!("http://localhost:{}", port);
{
let mut wc = WORKER_CONFIG.write().await;
(*wc).worker_tags = windmill_common::worker::DEFAULT_TAGS.clone();
(*wc).priority_tags_sorted = vec![windmill_common::worker::PriorityTags {
wc.worker_tags = windmill_common::worker::DEFAULT_TAGS.clone();
wc.priority_tags_sorted = vec![windmill_common::worker::PriorityTags {
priority: 0,
tags: (*wc).worker_tags.clone(),
tags: wc.worker_tags.clone(),
}];
windmill_common::worker::store_suspended_pull_query(&wc).await;
windmill_common::worker::store_pull_query(&wc).await;
@@ -349,7 +361,7 @@ fn find_module_in_vec(modules: Vec<FlowStatusModule>, id: &str) -> Option<FlowSt
modules.into_iter().find(|s| s.id() == id)
}
pub async fn set_jwt_secret() -> () {
pub async fn set_jwt_secret() {
let secret = "mytestsecret".to_string();
let mut l = JWT_SECRET.write().await;
*l = secret;
@@ -475,10 +487,10 @@ pub async fn assert_lockfile(
.await
.unwrap();
let mut completed = listen_for_completed_jobs(&db).await;
let mut completed = listen_for_completed_jobs(db).await;
let db2 = db.clone();
in_test_worker(
&db,
db,
async move {
completed.next().await; // deployed script
@@ -571,10 +583,10 @@ pub async fn run_deployed_relative_imports(
.await
.unwrap();
let mut completed = listen_for_completed_jobs(&db).await;
let mut completed = listen_for_completed_jobs(db).await;
let db2 = db.clone();
in_test_worker(
&db,
db,
async move {
completed.next().await; // deployed script
@@ -631,10 +643,10 @@ pub async fn run_preview_relative_imports(
let server = ApiServer::start(db.clone()).await?;
let port = server.addr.port();
let mut completed = listen_for_completed_jobs(&db).await;
let mut completed = listen_for_completed_jobs(db).await;
let db2 = db.clone();
in_test_worker(
&db,
db,
async move {
let job = RunJob::from(JobPayload::Code(RawCode {
hash: None,
@@ -671,3 +683,21 @@ pub async fn run_preview_relative_imports(
Ok(())
}
/// IMPORTANT!:
/// Do not run parallel in tests!
///
/// No tests can run this at the same time, will result into conflicts!!!
pub async fn rebuild_dmap(client: &windmill_api_client::Client) -> bool {
client
.client()
.post(format!(
"{}/w/test-workspace/workspaces/rebuild_dependency_map",
client.baseurl()
))
.send()
.await
.unwrap()
.status()
.is_success()
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -10,6 +10,7 @@ mod job_payload {
use windmill_common::jobs::JobPayload;
use windmill_common::flows::{FlowValue, FlowModule, FlowModuleValue};
use windmill_common::flow_status::RestartedFrom;
use windmill_common::worker::{
MIN_VERSION_IS_AT_LEAST_1_427, MIN_VERSION_IS_AT_LEAST_1_432, MIN_VERSION_IS_AT_LEAST_1_440,
};
File diff suppressed because it is too large Load Diff
+9 -12
View File
@@ -20,7 +20,6 @@ use windmill_common::flows::InputTransform;
#[cfg(any(feature = "python", feature = "deno_core"))]
use windmill_common::flow_status::RestartedFrom;
use windmill_common::{
flows::FlowValue,
jobs::{JobPayload, RawCode},
@@ -31,6 +30,8 @@ use common::*;
#[cfg(feature = "enterprise")]
use futures::StreamExt;
use windmill_common::flows::FlowModule;
use windmill_common::flows::FlowModuleValue;
// async fn _print_job(id: Uuid, db: &Pool<Postgres>) -> Result<(), anyhow::Error> {
// tracing::info!(
@@ -332,9 +333,6 @@ async fn test_identity(db: Pool<Postgres>) -> anyhow::Result<()> {
Ok(())
}
use windmill_common::flows::FlowModule;
use windmill_common::flows::FlowModuleValue;
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_deno_flow_same_worker(db: Pool<Postgres>) -> anyhow::Result<()> {
@@ -2083,14 +2081,14 @@ async fn test_flow_lock_all(db: Pool<Postgres>) -> anyhow::Result<()> {
language: windmill_api_client::types::RawScriptLanguage::Bash,
lock: Some(ref lock),
..
}) if lock == "")
}) if lock.is_empty())
|| matches!(
m.value,
windmill_api_client::types::FlowModuleValue::RawScript(RawScript{
language: windmill_api_client::types::RawScriptLanguage::Go | windmill_api_client::types::RawScriptLanguage::Python3 | windmill_api_client::types::RawScriptLanguage::Deno,
lock: Some(ref lock),
..
}) if lock.len() > 0),
}) if !lock.is_empty()),
"{:?}", m.value
);
});
@@ -2749,7 +2747,7 @@ async fn test_result_format(db: Pool<Postgres>) -> anyhow::Result<()> {
assert_eq!(job_result.get(), correct_result);
let response = windmill_api::jobs::run_wait_result(
&db.into(),
&db,
Uuid::parse_str(ordered_result_job_id).unwrap(),
"test-workspace".to_string(),
None,
@@ -2760,8 +2758,7 @@ async fn test_result_format(db: Pool<Postgres>) -> anyhow::Result<()> {
let result: Box<serde_json::value::RawValue> = serde_json::from_slice(
&axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap()
.to_vec(),
.unwrap(),
)
.unwrap();
assert_eq!(result.get(), correct_result);
@@ -2805,7 +2802,7 @@ async fn test_job_labels(db: Pool<Postgres>) -> anyhow::Result<()> {
restarted_from: None,
})
.arg("world", json!("you"))
.run_until_complete_with(&db, port, |id| async move {
.run_until_complete_with(db, port, |id| async move {
sqlx::query!(
"UPDATE v2_job SET labels = $2 WHERE id = $1 AND $2::TEXT[] IS NOT NULL",
id,
@@ -2871,7 +2868,7 @@ async fn test_workflow_as_code(db: Pool<Postgres>) -> anyhow::Result<()> {
// workflow as code require at least 2 workers:
let db = &db;
in_test_worker(
&db,
db,
async move {
let job = RunJob::from(JobPayload::Code(RawCode {
language: ScriptLang::Python3,
@@ -2879,7 +2876,7 @@ async fn test_workflow_as_code(db: Pool<Postgres>) -> anyhow::Result<()> {
..RawCode::default()
}))
.arg("n", json!(3))
.run_until_complete(&db, port)
.run_until_complete(db, port)
.await;
assert_eq!(job.json_result().unwrap(), json!(["OK", 3]));
+243 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.559.0
version: 1.562.0
title: Windmill API
contact:
@@ -4192,6 +4192,33 @@ paths:
application/json:
schema: {}
/w/{workspace}/resources/git_commit_hash/{path}:
get:
summary: get git repository latest commit hash
operationId: getGitCommitHash
tags:
- resource
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
- name: git_ssh_identity
in: query
schema:
type: string
responses:
"200":
description: git commit hash
content:
application/json:
schema:
type: object
properties:
commit_hash:
type: string
description: Latest commit hash from git ls-remote
required:
- commit_hash
/w/{workspace}/resources/exists/{path}:
get:
summary: does resource exists
@@ -13441,6 +13468,160 @@ paths:
schema:
$ref: "#/components/schemas/WindmillFilePreview"
/w/{workspace}/job_helpers/list_git_repo_files:
get:
summary: List the file keys available in instance object storage with resource-based access control
operationId: listGitRepoFiles
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: max_keys
in: query
required: true
schema:
type: integer
- name: marker
in: query
schema:
type: string
- name: prefix
in: query
required: false
schema:
type: string
description: Must follow format gitrepos/{workspace_id}/{resource_path}/...
- name: storage
in: query
schema:
type: string
responses:
"200":
description: List of file keys
content:
application/json:
schema:
type: object
properties:
next_marker:
type: string
windmill_large_files:
type: array
items:
$ref: "#/components/schemas/WindmillLargeFile"
restricted_access:
type: boolean
required:
- windmill_large_files
/w/{workspace}/job_helpers/load_git_repo_file_preview:
get:
summary: Load a preview of a file from instance storage with resource-based access control
operationId: loadGitRepoFilePreview
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: file_key
in: query
required: true
schema:
type: string
description: Must follow format gitrepos/{workspace_id}/{resource_path}/...
- name: file_size_in_bytes
in: query
schema:
type: integer
- name: file_mime_type
in: query
schema:
type: string
- name: csv_separator
in: query
schema:
type: string
- name: csv_has_header
in: query
schema:
type: boolean
- name: read_bytes_from
in: query
schema:
type: integer
- name: read_bytes_length
in: query
schema:
type: integer
- name: storage
in: query
schema:
type: string
responses:
"200":
description: FilePreview
content:
application/json:
schema:
$ref: "#/components/schemas/WindmillFilePreview"
/w/{workspace}/job_helpers/load_git_repo_file_metadata:
get:
summary: Load file metadata from instance storage with resource-based access control
operationId: loadGitRepoFileMetadata
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: file_key
in: query
required: true
schema:
type: string
description: Must follow format gitrepos/{workspace_id}/{resource_path}/...
- name: storage
in: query
schema:
type: string
responses:
"200":
description: FileMetadata
content:
application/json:
schema:
$ref: "#/components/schemas/WindmillFileMetadata"
/w/{workspace}/job_helpers/check_s3_folder_exists:
get:
summary: Check if S3 path exists and is a folder
operationId: checkS3FolderExists
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: file_key
description: S3 file key to check (e.g., gitrepos/{workspace_id}/u/user/resource/{commit_hash})
in: query
required: true
schema:
type: string
responses:
"200":
description: S3 folder existence check result
content:
application/json:
schema:
type: object
properties:
exists:
type: boolean
description: Whether the path exists
is_folder:
type: boolean
description: Whether the path is a folder (true) or file (false)
required:
- exists
- is_folder
/w/{workspace}/job_helpers/load_parquet_preview/{path}:
get:
summary: Load a preview of a parquet file
@@ -13677,6 +13858,67 @@ paths:
required:
- file_key
/w/{workspace}/job_helpers/upload_git_repo_file_to_instance_storage:
post:
summary: Upload a file to the instance storage gitrepos section for viewing
operationId: gitRepoViewerFileUpload
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: file_key
in: query
required: false
schema:
type: string
- name: file_extension
in: query
required: false
schema:
type: string
- name: s3_resource_path
in: query
required: false
schema:
type: string
- name: resource_type
in: query
required: false
schema:
type: string
- name: storage
in: query
schema:
type: string
- name: content_type
in: query
schema:
type: string
- name: content_disposition
in: query
schema:
type: string
requestBody:
description: File content
required: true
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
"200":
description: File upload status
content:
application/json:
schema:
type: object
properties:
file_key:
type: string
required:
- file_key
/w/{workspace}/job_helpers/download_s3_file:
get:
summary: Download file from S3 bucket
+2 -4
View File
@@ -1,7 +1,4 @@
use crate::{
db::{ApiAuthed, DB},
variables::get_variable_or_self,
};
use crate::db::{ApiAuthed, DB};
use axum::{body::Bytes, extract::Path, response::IntoResponse, routing::post, Extension, Router};
use http::{HeaderMap, Method};
@@ -9,6 +6,7 @@ use quick_cache::sync::Cache;
use reqwest::{Client, RequestBuilder};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use windmill_common::variables::get_variable_or_self;
use std::collections::HashMap;
use windmill_audit::{audit_oss::audit_log, ActionKind};
use windmill_common::ai_providers::{AIProvider, ProviderConfig, ProviderModel, AZURE_API_VERSION};
+13 -2
View File
@@ -60,7 +60,7 @@ use windmill_common::{
users::username_to_permissioned_as,
utils::{
http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, require_admin,
Pagination, RunnableKind, StripPath,
Pagination, RunnableKind, StripPath, WarnAfterExt,
},
variables::{build_crypt, build_crypt_with_key_suffix, encrypt},
worker::{to_raw_value, CLOUD_HOSTED},
@@ -699,7 +699,7 @@ async fn update_app_history(
check_scopes(&authed, || format!("apps:write:{}", &app_path))?;
sqlx::query!(
"INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
"INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
w_id,
app_path,
app_version,
@@ -1240,6 +1240,7 @@ async fn create_app_internal<'a>(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
@@ -1523,6 +1524,14 @@ async fn update_app_internal<'a>(
path.to_owned()
};
let v_id = if let Some(nvalue) = &ns.value {
// Row lock debounce key for path. We need this to make all updates of runnables sequential and predictable.
tokio::time::timeout(
core::time::Duration::from_secs(60),
windmill_common::jobs::lock_debounce_key(&w_id, &npath, &mut tx),
)
.warn_after_seconds(10)
.await??;
let app_id = sqlx::query_scalar!(
"SELECT id FROM app WHERE path = $1 AND workspace_id = $2",
npath,
@@ -1620,6 +1629,7 @@ async fn update_app_internal<'a>(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
@@ -1938,6 +1948,7 @@ async fn execute_component(
None,
false,
end_user_email,
None,
)
.await?;
tx.commit().await?;
@@ -159,22 +159,22 @@ async fn get_concurrent_intervals(
let lq = ListCompletedQuery { order_desc: Some(true), ..lq };
let lqc = lq.clone();
let lqq: ListQueueQuery = lqc.into();
let mut sqlb_q = SqlBuilder::select_from("v2_as_queue")
let mut sqlb_q = SqlBuilder::select_from("v2_job_queue")
.fields(UnifiedJob::queued_job_fields())
.order_by("created_at", lq.order_desc.unwrap_or(true))
.limit(row_limit)
.clone();
let mut sqlb_c = SqlBuilder::select_from("v2_as_completed_job")
let mut sqlb_c = SqlBuilder::select_from("v2_job_completed")
.fields(UnifiedJob::completed_job_fields())
.order_by("started_at", lq.order_desc.unwrap_or(true))
.limit(row_limit)
.clone();
let mut sqlb_q_user = SqlBuilder::select_from("v2_as_queue")
let mut sqlb_q_user = SqlBuilder::select_from("v2_job_queue")
.fields(&["id"])
.order_by("created_at", lq.order_desc.unwrap_or(true))
.limit(row_limit)
.clone();
let mut sqlb_c_user = SqlBuilder::select_from("v2_as_completed_job")
let mut sqlb_c_user = SqlBuilder::select_from("v2_job_completed")
.fields(&["id"])
.order_by("started_at", lq.order_desc.unwrap_or(true))
.limit(row_limit)
+1 -1
View File
@@ -173,7 +173,7 @@ async fn update_config(
let mut tx = db.begin().await?;
sqlx::query!(
"INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = $2",
"INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = EXCLUDED.config",
&name,
config
)
+1 -1
View File
@@ -212,7 +212,7 @@ pub async fn migrate(
if let Err(err) = sqlx::query!(
"DELETE FROM _sqlx_migrations WHERE
version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR
version=20250201145631 OR version=20250201145632"
version=20250201145631 OR version=20250201145632 OR version=20251006143821"
)
.execute(db)
.await
+1 -1
View File
@@ -79,7 +79,7 @@ async fn create_draft(
"INSERT INTO draft
(workspace_id, path, value, typ)
VALUES ($1, $2, $3::text::json, $4)
ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = $3::text::json",
ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = EXCLUDED.value",
&w_id,
draft.path,
//to preserve key orders
+15 -2
View File
@@ -32,7 +32,7 @@ use sql_builder::prelude::*;
use sqlx::{FromRow, Postgres, Transaction};
use windmill_audit::audit_oss::audit_log;
use windmill_audit::ActionKind;
use windmill_common::utils::query_elems_from_hub;
use windmill_common::utils::{query_elems_from_hub, WarnAfterExt};
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
use windmill_common::HUB_BASE_URL;
use windmill_common::{
@@ -546,6 +546,7 @@ async fn create_flow(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
@@ -722,7 +723,7 @@ async fn update_flow_history(
}
sqlx::query!(
"INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
"INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
w_id,
path_o.unwrap(),
version,
@@ -884,6 +885,15 @@ async fn update_flow(
.await?;
}
// Row lock debounce key for path. We need this to make all updates of runnables sequential and predictable.
tokio::time::timeout(
core::time::Duration::from_secs(60),
windmill_common::jobs::lock_debounce_key(&w_id, &nf.path, &mut tx),
)
.warn_after_seconds(10)
.await??;
// This will lock anyone who is trying to iterate on flow_versions with given path and parameters.
let version = sqlx::query_scalar!(
"INSERT INTO flow_version (workspace_id, path, value, schema, created_by) VALUES ($1, $2, $3, $4::text::json, $5) RETURNING id",
w_id,
@@ -900,6 +910,7 @@ async fn update_flow(
))
})?;
// TODO: This should happen only after we are done with dependency job.
sqlx::query!(
"UPDATE flow SET versions = array_append(versions, $1) WHERE path = $2 AND workspace_id = $3",
version, nf.path, w_id
@@ -1014,8 +1025,10 @@ async fn update_flow(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
sqlx::query!(
"UPDATE flow SET dependency_job = $1 WHERE path = $2 AND workspace_id = $3",
dependency_job_uuid,
+5 -1
View File
@@ -286,13 +286,17 @@ async fn create_igroup(
Extension(db): Extension<DB>,
Json(ng): Json<NewGroup>,
) -> Result<String> {
use uuid::Uuid;
require_super_admin(&db, &authed.email).await?;
let mut tx = db.begin().await?;
let id = Uuid::new_v4().to_string();
sqlx::query!(
"INSERT INTO instance_group (name, summary) VALUES ($1, $2) ON CONFLICT DO NOTHING",
"INSERT INTO instance_group (name, summary, id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
ng.name,
ng.summary,
id,
)
.execute(&mut *tx)
.await?;
+22 -1
View File
@@ -4101,6 +4101,7 @@ pub async fn run_flow_by_path_inner(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
@@ -4217,6 +4218,7 @@ pub async fn restart_flow(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -4320,6 +4322,7 @@ pub async fn run_script_by_path_inner(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -4473,6 +4476,7 @@ pub async fn run_workflow_as_code(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
@@ -5017,6 +5021,7 @@ pub async fn run_wait_result_job_by_path_get(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -5170,6 +5175,7 @@ pub async fn run_wait_result_script_by_path_internal(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -5287,6 +5293,7 @@ pub async fn run_wait_result_script_by_hash(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -5599,6 +5606,7 @@ pub async fn run_wait_result_flow_by_path_internal(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
@@ -5690,6 +5698,7 @@ async fn run_preview_script(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -5807,6 +5816,7 @@ async fn run_bundle_preview_script(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
job_id = Some(uuid);
@@ -5945,6 +5955,7 @@ async fn run_dependencies_job(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -6013,6 +6024,7 @@ async fn run_flow_dependencies_job(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -6357,6 +6369,7 @@ async fn run_preview_flow_job(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -6531,6 +6544,7 @@ async fn run_dynamic_select(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -6659,6 +6673,7 @@ pub async fn run_job_by_hash_inner(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
tx.commit().await?;
@@ -6889,6 +6904,12 @@ enum JobUpdateSSEStream {
Ping,
}
lazy_static::lazy_static! {
pub static ref TIMEOUT_SSE_STREAM: u64 =
std::env::var("TIMEOUT_SSE_STREAM").unwrap_or("60".to_string()).parse::<u64>().unwrap_or(60);
}
fn start_job_update_sse_stream(
opt_authed: Option<ApiAuthed>,
opt_tokened: OptTokened,
@@ -7021,7 +7042,7 @@ fn start_job_update_sse_stream(
last_ping = Instant::now();
}
if start.elapsed().as_secs() > 30 {
if start.elapsed().as_secs() > *TIMEOUT_SSE_STREAM {
if tx.send(JobUpdateSSEStream::Timeout).await.is_err() {
tracing::warn!("Failed to send job timeout for job {job_id}");
}
+217 -4
View File
@@ -13,6 +13,7 @@ use crate::{
users::{maybe_refresh_folders, require_owner_of_path, Tokened},
utils::{check_scopes, require_super_admin, BulkDeleteRequest},
var_resource_cache::{cache_resource, get_cached_resource},
variables::get_value_internal,
webhook_util::{WebhookMessage, WebhookShared},
};
use axum::{
@@ -28,16 +29,18 @@ use serde::{Deserialize, Serialize};
use serde_json::{value::RawValue, Value};
use sql_builder::{bind::Bind, quote, SqlBuilder};
use sqlx::{FromRow, Postgres, Transaction};
use std::process::Stdio;
use tokio::process::Command;
use uuid::Uuid;
use windmill_audit::audit_oss::{audit_log, AuditAuthor};
use windmill_audit::ActionKind;
use windmill_common::{
db::{UserDB, UserDbWithOptAuthed},
error::{Error, JsonResult, Result},
db::{UserDB, UserDbWithAuthed, UserDbWithOptAuthed},
error::{self, Error, JsonResult, Result},
get_database_url, parse_postgres_url,
utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath},
variables,
worker::CLOUD_HOSTED,
worker::{CLOUD_HOSTED, TMP_DIR},
workspaces::get_ducklake_instance_pg_catalog_password,
};
@@ -58,6 +61,7 @@ pub fn workspaced_service() -> Router {
.route("/delete/*path", delete(delete_resource))
.route("/delete_bulk", delete(delete_resources_bulk))
.route("/create", post(create_resource))
.route("/git_commit_hash/*path", get(get_git_commit_hash))
.route("/type/list", get(list_resource_types))
.route("/type/listnames", get(list_resource_types_names))
.route("/type/get/:name", get(get_resource_type))
@@ -737,7 +741,7 @@ async fn create_resource(
"INSERT INTO resource
(workspace_id, path, value, description, resource_type, created_by, edited_at)
VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)
DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()",
DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now()",
w_id,
resource.path,
raw_json as sqlx::types::Json<&RawValue>,
@@ -1387,3 +1391,212 @@ where
Ok(resource)
}
#[derive(Deserialize, Serialize)]
struct GitRepositoryResource {
url: String,
#[serde(skip_serializing_if = "Option::is_none")]
branch: Option<String>,
}
#[derive(Serialize)]
struct GitCommitHashResponse {
commit_hash: String,
}
#[derive(Deserialize)]
struct GitCommitHashQuery {
git_ssh_identity: Option<String>,
}
async fn get_git_commit_hash(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Tokened { token }: Tokened,
Path((w_id, path)): Path<(String, StripPath)>,
Query(query): Query<GitCommitHashQuery>,
) -> JsonResult<GitCommitHashResponse> {
let path = path.to_path();
check_scopes(&authed, || format!("resources:read:{}", path))?;
let git_repo_resource_value = get_resource_value_interpolated_internal(
&authed,
Some(user_db.clone()),
&db,
&w_id,
path,
None,
&token,
false,
)
.await
.map_err(|e| Error::NotAuthorized(format!("Access to resource {} denied: ({e})", path)))?;
let git_resource: GitRepositoryResource = match git_repo_resource_value {
Some(value) => serde_json::from_value(value).map_err(|e| {
Error::BadRequest(format!("Invalid git repository resource format: {}", e))
})?,
None => return Err(Error::NotFound(format!("Resource {} not found", path)).into()),
};
let identities: Vec<String> = query
.git_ssh_identity
.map(|s| s.split(",").map(|s| s.to_string()).collect())
.unwrap_or(vec![]);
let (git_ssh_cmd, filenames) =
get_git_ssh_cmd(&authed, &user_db, &db, &w_id, identities).await?;
let commit_hash = get_repo_latest_commit_hash(&git_resource, git_ssh_cmd).await;
delete_paths(&filenames).await;
Ok(Json(GitCommitHashResponse { commit_hash: commit_hash? }))
}
async fn write_ssh_file(
authed: &ApiAuthed,
user_db: &UserDB,
db: &DB,
w_id: &str,
var_path: &str,
) -> std::result::Result<std::path::PathBuf, (error::Error, std::path::PathBuf)> {
let id_file_name = format!(".ssh_id_priv_{}", Uuid::new_v4());
let loc = std::path::Path::new(TMP_DIR)
.join("ssh_ids")
.join(id_file_name);
let userdb_authed = UserDbWithAuthed { db: user_db.clone(), authed: &authed.to_authed_ref() };
let mut content = get_value_internal(&userdb_authed, db, w_id, var_path, authed, false)
.await
.map_err(|e| {
(
error::Error::NotFound(format!(
"Variable {var_path} not found for git ssh identity: {e:#}"
)),
loc.clone(),
)
})?;
content.push_str("\n");
if let Some(p) = &loc.parent() {
tokio::fs::create_dir_all(p)
.await
.map_err(|e| (e.into(), loc.clone()))?;
}
tokio::fs::write(&loc, content)
.await
.map_err(|e| (e.into(), loc.clone()))?;
#[cfg(unix)]
{
let perm = std::os::unix::fs::PermissionsExt::from_mode(0o600);
tokio::fs::set_permissions(&loc, perm)
.await
.map_err(|e| (e.into(), loc.clone()))?;
}
return Ok(loc);
}
async fn delete_paths(paths: &Vec<std::path::PathBuf>) {
for path in paths {
let _ = tokio::fs::remove_file(&path).await;
}
}
async fn get_git_ssh_cmd(
authed: &ApiAuthed,
user_db: &UserDB,
db: &DB,
w_id: &str,
git_ssh_identity: Vec<String>,
) -> error::Result<(Option<String>, Vec<std::path::PathBuf>)> {
if git_ssh_identity.len() > 5 {
return Err(error::Error::BadRequest(
"Too many ssh identities, try using at most 1".to_string(),
));
}
if git_ssh_identity.len() == 0 {
return Ok((None, vec![]));
}
let mut ssh_id_files = vec![];
let mut file_paths = vec![];
for var_path in git_ssh_identity.iter() {
match write_ssh_file(authed, user_db, db, w_id, &var_path).await {
Ok(loc) => {
ssh_id_files.push(format!(
" -i '{}'",
loc.to_string_lossy().replace('\'', r"'\''")
));
file_paths.push(loc);
}
Err((e, loc)) => {
file_paths.push(loc);
delete_paths(&file_paths).await;
return Err(e);
}
}
}
let git_ssh_cmd = format!("ssh -o StrictHostKeyChecking=no{}", ssh_id_files.join(""));
Ok((Some(git_ssh_cmd), file_paths))
}
async fn get_repo_latest_commit_hash(
git_resource: &GitRepositoryResource,
git_ssh_command: Option<String>,
) -> Result<String> {
let mut git_cmd = Command::new("git");
let ref_spec = git_resource
.branch
.as_deref()
.filter(|s| !s.is_empty())
.unwrap_or("HEAD");
git_cmd.args(["ls-remote", &git_resource.url, ref_spec]);
if let Some(git_ssh_command) = git_ssh_command {
git_cmd.env("GIT_SSH_COMMAND", git_ssh_command);
}
git_cmd.stderr(Stdio::piped());
let output = git_cmd
.output()
.await
.map_err(|e| Error::internal_err(format!("Failed to execute git command: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8(output.stderr)
.unwrap_or_else(|_| "Failed to decode stderr".to_string());
return Err(Error::BadRequest(format!(
"Error getting git repo commit hash: {}",
stderr
)));
}
let stdout = String::from_utf8(output.stdout)
.map_err(|e| Error::internal_err(format!("Failed to decode git output: {}", e)))?;
let lines: Vec<&str> = stdout.lines().collect();
if lines.is_empty() {
return Err(Error::BadRequest(format!(
"No commits found for reference '{}' in repository '{}'",
ref_spec, git_resource.url
)));
}
let commit_hash = lines
.first()
.and_then(|line| line.split_whitespace().next())
.map(|s| s.to_string())
.ok_or_else(|| {
Error::BadRequest("Unexpected output format for git ls-remote".to_string())
})?;
Ok(commit_hash)
}
+12 -1
View File
@@ -769,6 +769,14 @@ async fn create_script_internal<'c>(
}
};
// Row lock debounce key for path. We need this to make all updates of runnables sequential and predictable.
tokio::time::timeout(
core::time::Duration::from_secs(60),
windmill_common::jobs::lock_debounce_key(&w_id, &ns.path, &mut tx),
)
.warn_after_seconds(10)
.await??;
sqlx::query!(
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
@@ -817,6 +825,7 @@ async fn create_script_internal<'c>(
)
.execute(&mut *tx)
.await?;
let p_path_opt = parent_hashes_and_perms.as_ref().map(|x| x.p_path.clone());
if let Some(ref p_path) = p_path_opt {
sqlx::query!(
@@ -1000,6 +1009,7 @@ async fn create_script_internal<'c>(
Some(&authed.clone().into()),
false,
None,
None,
)
.await?;
Ok((hash, new_tx, None))
@@ -1249,7 +1259,8 @@ async fn update_script_history(
let mut tx = user_db.begin(&authed).await?;
sqlx::query!(
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4",
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL
DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
w_id,
script_path,
script_hash.0,
+5 -1
View File
@@ -218,6 +218,10 @@ pub struct Value {
}
pub async fn delete_global_setting(db: &DB, key: &str) -> error::Result<()> {
if key == "ducklake_user_pg_pwd" || key == "ducklake_settings" {
tracing::error!("Tried to unset global setting {}, ignored", key);
return Ok(());
}
sqlx::query!("DELETE FROM global_settings WHERE name = $1", key,)
.execute(db)
.await?;
@@ -334,7 +338,7 @@ pub async fn set_global_setting_internal(
}
v => {
sqlx::query!(
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()",
key,
v
)
@@ -861,6 +861,7 @@ async fn trigger_script_with_retry_and_error_handler(
push_authed.as_ref(),
false,
None,
None,
)
.await?;
tx.commit().await?;
+30 -14
View File
@@ -583,7 +583,7 @@ async fn update_tutorial_progress(
Json(progress): Json<Progress>,
) -> Result<String> {
sqlx::query_scalar!(
"INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = $1::bigint::bit(64)",
"INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = EXCLUDED.progress",
progress.progress as i64,
authed.email
)
@@ -1385,7 +1385,9 @@ async fn convert_user_to_group(
// Check if user is already a group user
if let Some(added_via) = &user_info.added_via {
if added_via.get("source").and_then(|v| v.as_str()) == Some("instance_group") {
return Err(Error::BadRequest("User is already a group user".to_string()));
return Err(Error::BadRequest(
"User is already a group user".to_string(),
));
}
}
@@ -1408,16 +1410,18 @@ async fn convert_user_to_group(
if eligible_groups.is_empty() {
return Err(Error::BadRequest(
"User is not a member of any instance groups configured for auto-add in this workspace".to_string()
"User is not a member of any instance groups configured for auto-add in this workspace"
.to_string(),
));
}
// Determine the group with highest precedence (same logic as process_instance_group_auto_adds)
let roles: std::collections::HashMap<String, String> = if let Some(roles_json) = &eligible_groups[0].auto_add_instance_groups_roles {
serde_json::from_value(roles_json.clone()).unwrap_or_default()
} else {
std::collections::HashMap::new()
};
let roles: std::collections::HashMap<String, String> =
if let Some(roles_json) = &eligible_groups[0].auto_add_instance_groups_roles {
serde_json::from_value(roles_json.clone()).unwrap_or_default()
} else {
std::collections::HashMap::new()
};
let mut best_group = &eligible_groups[0].group_name;
let mut best_precedence = 0u8;
@@ -1443,7 +1447,10 @@ async fn convert_user_to_group(
// Determine role from group configuration using the selected primary group
let default_role = "developer".to_string();
let role = roles.get(primary_group_name).unwrap_or(&default_role).as_str();
let role = roles
.get(primary_group_name)
.unwrap_or(&default_role)
.as_str();
let (is_admin, is_operator) = match role {
"admin" => (true, false),
@@ -1487,12 +1494,18 @@ async fn convert_user_to_group(
&db,
&w_id,
windmill_git_sync::DeployedObject::User { email: user_info.email.clone() },
Some(format!("Converted user '{}' to group user (group: {}, role: {})", &user_info.email, primary_group_name, role)),
Some(format!(
"Converted user '{}' to group user (group: {}, role: {})",
&user_info.email, primary_group_name, role
)),
true,
)
.await?;
Ok(format!("User {} converted to group user (group: {}, role: {})", username_to_convert, primary_group_name, role))
Ok(format!(
"User {} converted to group user (group: {}, role: {})",
username_to_convert, primary_group_name, role
))
}
async fn update_user(
@@ -1594,9 +1607,12 @@ async fn delete_user(
}
// Remove user from all instance groups email_to_igroup
sqlx::query!("DELETE FROM email_to_igroup WHERE email = $1", &email_to_delete)
.execute(&mut *tx)
.await?;
sqlx::query!(
"DELETE FROM email_to_igroup WHERE email = $1",
&email_to_delete
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
-33
View File
@@ -864,36 +864,3 @@ pub async fn get_value_internal<'a, 'e, A: sqlx::Acquire<'e, Database = Postgres
Ok(r)
}
pub async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result<String> {
if !path.starts_with("$var:") {
return Ok(path);
}
let path = path.strip_prefix("$var:").unwrap().to_string();
let record = sqlx::query!(
"SELECT value, is_secret
FROM variable
WHERE path = $1 AND workspace_id = $2",
&path,
&w_id
)
.fetch_optional(db)
.await?;
if let Some(record) = record {
let mut value = record.value;
if record.is_secret {
let mc = build_crypt(db, w_id).await?;
value = decrypt(&mc, value).map_err(|e| {
Error::internal_err(format!("Error decrypting variable {}: {}", path, e))
})?;
}
Ok(value)
} else {
Err(Error::NotFound(format!(
"Variable not found when resolving `$var:{}`",
path
)))
}
}

Some files were not shown because too many files have changed in this diff Show More