From 92c601860f1e1211aa34838cd08900ba7334a20c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 11:15:01 +0000 Subject: [PATCH 01/93] fix: otel gracefully handle no native ts --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index c3e5ea69bb..93c499397d 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -b2bd490cd14e7794bebbd8737c2833ea671370b3 \ No newline at end of file +ab082a1a65830bf362a32b30fa86741999da091d \ No newline at end of file From 097d9288c58076882f1991e2fb33e4441fe332d3 Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Wed, 11 Feb 2026 12:18:51 +0100 Subject: [PATCH 02/93] feat(uv): index resolve strategy (#7885) * fix * fix Signed-off-by: pyranota * Update backend/windmill-worker/src/python_executor.rs Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> * Update backend/windmill-worker/src/python_executor.rs Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --------- Signed-off-by: pyranota Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- backend/src/main.rs | 7 ++--- backend/src/monitor.rs | 14 +++++++++- .../windmill-common/src/global_settings.rs | 1 + .../nsjail/download_deps.py.sh | 15 +++++------ .../windmill-worker/src/python_executor.rs | 24 +++++++++-------- backend/windmill-worker/src/worker.rs | 1 + .../src/lib/components/InstanceSetting.svelte | 2 +- .../src/lib/components/instanceSettings.ts | 26 +++++++++++++++++++ 8 files changed, 66 insertions(+), 24 deletions(-) diff --git a/backend/src/main.rs b/backend/src/main.rs index 4a7b23aab7..083575e418 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -46,8 +46,8 @@ use windmill_common::{ KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, - OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING, - POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, UV_INDEX_STRATEGY_SETTING, + POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, RUBY_REPOS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING, @@ -103,7 +103,7 @@ use crate::monitor::{ reload_jwt_secret_setting, reload_license_key, reload_npm_config_registry_setting, reload_otel_tracing_proxy_setting, reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config, - reload_worker_config, MonitorIteration, + reload_uv_index_strategy_setting, reload_worker_config, MonitorIteration, }; #[cfg(feature = "parquet")] @@ -1564,6 +1564,7 @@ async fn process_notify_event( SCIM_TOKEN_SETTING => reload_scim_token_setting(conn).await, EXTRA_PIP_INDEX_URL_SETTING => reload_extra_pip_index_url_setting(conn).await, PIP_INDEX_URL_SETTING => reload_pip_index_url_setting(conn).await, + UV_INDEX_STRATEGY_SETTING => reload_uv_index_strategy_setting(conn).await, INSTANCE_PYTHON_VERSION_SETTING => { reload_instance_python_version_setting(conn).await } diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index b854516b69..09cc8ad66d 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -57,6 +57,7 @@ use windmill_common::{ JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, + UV_INDEX_STRATEGY_SETTING, POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING, @@ -88,7 +89,7 @@ use windmill_worker::{ SameWorkerSender, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NUGET_CONFIG, OTEL_TRACING_PROXY_SETTINGS, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, POWERSHELL_REPO_PAT, - POWERSHELL_REPO_URL, + POWERSHELL_REPO_URL, UV_INDEX_STRATEGY, }; #[cfg(feature = "parquet")] @@ -317,6 +318,7 @@ pub async fn initial_load( reload_job_default_timeout_setting(&conn).await; reload_extra_pip_index_url_setting(&conn).await; reload_pip_index_url_setting(&conn).await; + reload_uv_index_strategy_setting(&conn).await; reload_npm_config_registry_setting(&conn).await; reload_bunfig_install_scopes_setting(&conn).await; reload_instance_python_version_setting(&conn).await; @@ -1250,6 +1252,16 @@ pub async fn reload_pip_index_url_setting(conn: &Connection) { .await; } +pub async fn reload_uv_index_strategy_setting(conn: &Connection) { + reload_option_setting_with_tracing( + conn, + UV_INDEX_STRATEGY_SETTING, + "UV_INDEX_STRATEGY", + UV_INDEX_STRATEGY.clone(), + ) + .await; +} + pub async fn reload_instance_python_version_setting(conn: &Connection) { reload_option_setting_with_tracing( conn, diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 59a941701b..f1a1613ed2 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -19,6 +19,7 @@ pub const RUBY_REPOS_SETTING: &str = "ruby_repos"; pub const EXTRA_PIP_INDEX_URL_SETTING: &str = "pip_extra_index_url"; pub const PIP_INDEX_URL_SETTING: &str = "pip_index_url"; +pub const UV_INDEX_STRATEGY_SETTING: &str = "uv_index_strategy"; pub const INSTANCE_PYTHON_VERSION_SETTING: &str = "instance_python_version"; pub const SCIM_TOKEN_SETTING: &str = "scim_token"; pub const SAML_METADATA_SETTING: &str = "saml_metadata"; diff --git a/backend/windmill-worker/nsjail/download_deps.py.sh b/backend/windmill-worker/nsjail/download_deps.py.sh index 06a61ac19f..ce056e7e76 100755 --- a/backend/windmill-worker/nsjail/download_deps.py.sh +++ b/backend/windmill-worker/nsjail/download_deps.py.sh @@ -19,17 +19,16 @@ then echo "\$TRUSTED_HOST is set to $TRUSTED_HOST" fi -CMD="/usr/local/bin/uv pip install -\"$REQ\" ---target \"$TARGET\" ---no-cache +CMD="/usr/local/bin/uv pip install +\"$REQ\" +--target \"$TARGET\" +--no-cache --no-config ---no-color ---no-deps +--no-color +--no-deps --link-mode=copy -$PY_PATH +$PY_PATH $INDEX_URL_ARG $EXTRA_INDEX_URL_ARG $TRUSTED_HOST_ARG ---index-strategy unsafe-best-match --system --reinstall " diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 966a76d90d..8dc689a3c2 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -135,6 +135,7 @@ use crate::{ worker_utils::ping_job_status, PyV, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV, UV_CACHE_DIR, + UV_INDEX_STRATEGY, }; use windmill_common::client::AuthedClient; @@ -221,6 +222,9 @@ pub async fn uv_pip_compile( requirements.to_string() }; + let uv_index_strategy = UV_INDEX_STRATEGY.read().await.clone(); + let uv_index_strategy = uv_index_strategy.as_deref().unwrap_or("unsafe-best-match"); + let py_version_str = py_version.clone().to_string(); // Include python version to requirements.in // We need it because same hash based on requirements.in can get calculated even for different python versions @@ -230,7 +234,7 @@ pub async fn uv_pip_compile( #[cfg(feature = "enterprise")] let requirements = replace_pip_secret(conn, w_id, &requirements, worker_name, job_id).await?; - let req_hash = format!("py-{}", calculate_hash(&requirements)); + let req_hash = format!("py-{}-{uv_index_strategy}", calculate_hash(&requirements)); if !no_cache { if let Some(db) = conn.as_sql() { @@ -271,11 +275,6 @@ pub async fn uv_pip_compile( "--strip-extras", "-o", "requirements.txt", - // Prefer main index over extra - // https://docs.astral.sh/uv/pip/compatibility/#packages-that-exist-on-multiple-indexes - // TODO: Use env variable that can be toggled from UI - "--index-strategy", - "unsafe-best-match", // Target to /tmp/windmill/cache/uv "--cache-dir", UV_CACHE_DIR, @@ -321,6 +320,7 @@ pub async fn uv_pip_compile( #[cfg(unix)] let uv_cmd = UV_PATH.as_str(); + let mut child_cmd = Command::new(uv_cmd); child_cmd .current_dir(job_dir) @@ -328,6 +328,7 @@ pub async fn uv_pip_compile( .env("HOME", HOME_ENV.to_string()) .env("PATH", PATH_ENV.to_string()) .env("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR.to_string()) + .env("UV_INDEX_STRATEGY", uv_index_strategy) .envs(PROXY_ENVS.clone()) .args(&args) .stdout(Stdio::piped()) @@ -1353,6 +1354,9 @@ async fn spawn_uv_install( py_path: Option, worker_dir: &str, ) -> Result, Error> { + let uv_index_strategy_guard = UV_INDEX_STRATEGY.read().await.clone(); + let uv_index_strategy = uv_index_strategy_guard.as_deref().unwrap_or("unsafe-best-match"); + if !*DISABLE_NSJAIL { tracing::info!( workspace_id = %w_id, @@ -1375,6 +1379,7 @@ async fn spawn_uv_install( if *NATIVE_CERT { vars.push(("UV_NATIVE_TLS", "true")); } + let _owner; if let Some(py_path) = py_path.as_ref() { _owner = format!( @@ -1385,6 +1390,7 @@ async fn spawn_uv_install( } vars.push(("REQ", &req)); vars.push(("TARGET", venv_p)); + vars.push(("UV_INDEX_STRATEGY", uv_index_strategy)); std::fs::create_dir_all(venv_p)?; let nsjail_proto = format!("{req}.config.proto"); @@ -1430,11 +1436,6 @@ async fn spawn_uv_install( "--no-config", "--link-mode=copy", "--system", - // Prefer main index over extra - // https://docs.astral.sh/uv/pip/compatibility/#packages-that-exist-on-multiple-indexes - // TODO: Use env variable that can be toggled from UI - "--index-strategy", - "unsafe-best-match", "--target", venv_p, "--no-cache", @@ -1464,6 +1465,7 @@ async fn spawn_uv_install( let mut envs = vec![("PATH", PATH_ENV.as_str())]; envs.push(("HOME", HOME_ENV.as_str())); + envs.push(("UV_INDEX_STRATEGY", uv_index_strategy)); if let Some(url) = pip_index_url.as_ref() { command_args.extend(["--index-url", url]); diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 06f59e3814..fad2878477 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -584,6 +584,7 @@ lazy_static::lazy_static! { pub static ref PIP_EXTRA_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); pub static ref PIP_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); + pub static ref UV_INDEX_STRATEGY: Arc>> = Arc::new(RwLock::new(None)); pub static ref INSTANCE_PYTHON_VERSION: Arc>> = Arc::new(RwLock::new(None)); pub static ref JOB_DEFAULT_TIMEOUT: Arc>> = Arc::new(RwLock::new(None)); diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index 0be0c05891..fe165fa636 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -50,7 +50,7 @@ (setting.fieldType == 'select' || setting.fieldType == 'select_python') && $values[setting.key] == undefined ) { - $values[setting.key] = 'default' + $values[setting.key] = setting.defaultValue ? setting.defaultValue() : 'default' } let latestKeyRenewalAttempt: { diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 1b29688316..c722815fe0 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -329,6 +329,32 @@ export const settings: Record = { storage: 'setting', ee_only: '' }, + { + label: 'UV index strategy', + description: + 'Strategy for resolving packages from multiple indexes. See uv docs', + key: 'uv_index_strategy', + fieldType: 'select', + placeholder: 'unsafe-best-match', + defaultValue: () => "unsafe-best-match", + select_items: [ + { + label: 'first-index', + tooltip: 'Only use the first index that contains the package' + }, + { + label: 'unsafe-first-match', + tooltip: 'Search for packages across all indexes, preferring the first match' + }, + { + label: 'unsafe-best-match (default)', + value: 'unsafe-best-match', + tooltip: 'Search for packages across all indexes, preferring the best match' + } + ], + storage: 'setting', + ee_only: '' + }, { label: 'Npm config registry', description: 'Add private npm registry', From 754b48cb898dffe196339cea1c1598c9e1765cdc Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 11 Feb 2026 12:41:37 +0100 Subject: [PATCH 03/93] fix: waitJob getJob and streamJob in raw apps (#7901) * fix: waitJob getJob and streamJob in raw apps * nits * use latest ui builder * fix --------- Co-authored-by: Ruben Fiszel --- frontend/scripts/untar_ui_builder.js | 2 +- .../raw_apps/RawAppBackgroundRunner.svelte | 3 ++- frontend/src/lib/rawAppWmillTs.ts | 26 ++++++++++--------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index bb3a6ada33..0991589025 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -20,7 +20,7 @@ console.log('Running postinstall for root project'); import { x } from 'tar' -const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-1fcd9b7.tar.gz' +const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-0e5c66f.tar.gz' const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz') const extractTo = path.join(process.cwd(), 'static/ui_builder/') diff --git a/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte b/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte index 7ca4b15ffd..389697707e 100644 --- a/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte @@ -44,7 +44,7 @@ result = e } - if (event.data.type == 'backend') { + if (event.data.type == 'backend' || event.data.type == 'waitJob') { respond({ result, error }) } if (editor) { @@ -134,6 +134,7 @@ const reqId = data.reqId const params = new URLSearchParams() params.set('fast', 'true') + params.set('only_result', 'true') const sseUrl = `/api/w/${workspace}/jobs_u/getupdate_sse/${jobId}?${params.toString()}` const eventSource = new EventSource(sseUrl) diff --git a/frontend/src/lib/rawAppWmillTs.ts b/frontend/src/lib/rawAppWmillTs.ts index 3cf719a0a0..64c23672c4 100644 --- a/frontend/src/lib/rawAppWmillTs.ts +++ b/frontend/src/lib/rawAppWmillTs.ts @@ -1,10 +1,11 @@ let reqs: Record = {} -function doRequest(type: string, o: object) { +function doRequest(type: string, o: object, extra?: object) { return new Promise((resolve, reject) => { const reqId = Math.random().toString(36) - reqs[reqId] = { resolve, reject } - parent.postMessage({ ...o, type, reqId }, '*') + reqs[reqId] = { resolve, reject, ...extra } + const req = { ...o, type, reqId } + parent.postMessage(req, '*') }) } @@ -49,15 +50,11 @@ export function streamJob( jobId: string, onUpdate?: (data: { new_result_stream?: string; stream_offset?: number }) => void ): Promise { - return new Promise((resolve, reject) => { - const reqId = Math.random().toString(36) - reqs[reqId] = { resolve, reject, onUpdate } - parent.postMessage({ jobId, type: 'streamJob', reqId }, '*') - }) + return doRequest('streamJob', { jobId }, { onUpdate }) } window.addEventListener('message', (e) => { - if (e.data.type == 'streamJobUpdate') { + if (e.data.type === 'streamJobUpdate') { // Handle streaming update let job = reqs[e.data.reqId] if (job && job.onUpdate) { @@ -66,7 +63,7 @@ window.addEventListener('message', (e) => { stream_offset: e.data.stream_offset }) } - } else if (e.data.type == 'streamJobRes') { + } else if (e.data.type === 'streamJobRes') { // Handle stream completion let job = reqs[e.data.reqId] if (job) { @@ -77,7 +74,12 @@ window.addEventListener('message', (e) => { } delete reqs[e.data.reqId] } - } else if (e.data.type == 'backendRes' || e.data.type == 'backendAsyncRes') { + } else if ( + e.data.type === 'backendRes' || + e.data.type === 'backendAsyncRes' || + e.data.type === 'waitJobRes' || + e.data.type === 'getJobRes' + ) { console.log('Message from parent backend', e.data) let job = reqs[e.data.reqId] if (job) { @@ -87,9 +89,9 @@ window.addEventListener('message', (e) => { } else { job.resolve(result) } + delete reqs[e.data.reqId] } else { console.error('No job found for', e.data.reqId) } - delete reqs[e.data.reqId] } }) From f22eb964e47defe9922ecdb6ab471dd4ca267952 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:01:46 +0100 Subject: [PATCH 04/93] feat(ai): support 1M context window for Anthropic resources (#7891) * feat(ai): support 1M context window for Anthropic resources Add `enable_1m_context` boolean field to Anthropic resource configuration. When enabled (and not using Vertex AI), sends the `anthropic-beta: context-1m-2025-08-07` header in both the API proxy layer and the AI agent worker layer. Co-Authored-By: Claude Opus 4.6 * fix(ai): add serde alias for enable_1M_context DB field name The resource_type schema uses `enable_1M_context` (uppercase M) but serde only matched `enable_1m_context` and `enable1mContext`, causing the field to always deserialize as false. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- backend/windmill-api/src/ai.rs | 13 +++++++++++++ .../src/ai/providers/anthropic.rs | 17 +++++++++++++---- backend/windmill-worker/src/ai/query_builder.rs | 1 + backend/windmill-worker/src/ai/types.rs | 7 +++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/backend/windmill-api/src/ai.rs b/backend/windmill-api/src/ai.rs index 34e11e692a..94ec94fb60 100644 --- a/backend/windmill-api/src/ai.rs +++ b/backend/windmill-api/src/ai.rs @@ -169,6 +169,9 @@ struct AIStandardResource { /// Platform for Anthropic API (standard or google_vertex_ai) #[serde(default)] platform: AnthropicPlatform, + /// Enable 1M context window for Anthropic + #[serde(alias = "enable_1M_context", default)] + enable_1m_context: bool, } #[derive(Deserialize, Debug)] @@ -197,6 +200,7 @@ struct AIRequestConfig { #[allow(dead_code)] pub aws_secret_access_key: Option, pub platform: AnthropicPlatform, + pub enable_1m_context: bool, } impl AIRequestConfig { @@ -216,10 +220,12 @@ impl AIRequestConfig { aws_access_key_id, aws_secret_access_key, platform, + enable_1m_context, ) = match resource { AIResource::Standard(resource) => { let region = resource.region.clone(); let platform = resource.platform.clone(); + let enable_1m_context = resource.enable_1m_context; // Skip get_base_url for Bedrock - it uses SDK directly, not HTTP let base_url = if matches!(provider, AIProvider::AWSBedrock) { String::new() @@ -258,6 +264,7 @@ impl AIRequestConfig { aws_access_key_id, aws_secret_access_key, platform, + enable_1m_context, ) } AIResource::OAuth(resource) => { @@ -279,6 +286,7 @@ impl AIRequestConfig { None, None, AnthropicPlatform::Standard, + false, ) } }; @@ -293,6 +301,7 @@ impl AIRequestConfig { aws_access_key_id, aws_secret_access_key, platform, + enable_1m_context, }) } @@ -394,6 +403,10 @@ impl AIRequestConfig { } } + if is_anthropic_sdk && self.enable_1m_context { + request = request.header("anthropic-beta", "context-1m-2025-08-07"); + } + // Add authentication headers if let Some(api_key) = self.api_key { if is_azure { diff --git a/backend/windmill-worker/src/ai/providers/anthropic.rs b/backend/windmill-worker/src/ai/providers/anthropic.rs index d8ce9ffc84..e42e1dc49b 100644 --- a/backend/windmill-worker/src/ai/providers/anthropic.rs +++ b/backend/windmill-worker/src/ai/providers/anthropic.rs @@ -354,11 +354,16 @@ pub struct AnthropicQueryBuilder { #[allow(dead_code)] provider_kind: AIProvider, platform: AnthropicPlatform, + enable_1m_context: bool, } impl AnthropicQueryBuilder { - pub fn new(provider_kind: AIProvider, platform: AnthropicPlatform) -> Self { - Self { provider_kind, platform } + pub fn new( + provider_kind: AIProvider, + platform: AnthropicPlatform, + enable_1m_context: bool, + ) -> Self { + Self { provider_kind, platform, enable_1m_context } } fn is_vertex(&self) -> bool { @@ -582,10 +587,14 @@ impl QueryBuilder for AnthropicQueryBuilder { vec![("Authorization", format!("Bearer {}", api_key))] } else { // Standard Anthropic API uses x-api-key and anthropic-version header - vec![ + let mut headers = vec![ ("x-api-key", api_key.to_string()), ("anthropic-version", ANTHROPIC_VERSION_STANDARD.to_string()), - ] + ]; + if self.enable_1m_context { + headers.push(("anthropic-beta", "context-1m-2025-08-07".to_string())); + } + headers } } } diff --git a/backend/windmill-worker/src/ai/query_builder.rs b/backend/windmill-worker/src/ai/query_builder.rs index 10e510a492..97176869c5 100644 --- a/backend/windmill-worker/src/ai/query_builder.rs +++ b/backend/windmill-worker/src/ai/query_builder.rs @@ -119,6 +119,7 @@ pub fn create_query_builder(provider: &ProviderWithResource) -> Box Box::new(AnthropicQueryBuilder::new( provider.kind.clone(), provider.get_platform().clone(), + provider.get_enable_1m_context(), )), AIProvider::OpenRouter => Box::new(OpenRouterQueryBuilder::new()), // All other providers use the completion endpoint diff --git a/backend/windmill-worker/src/ai/types.rs b/backend/windmill-worker/src/ai/types.rs index 4177680e43..56e1571a6b 100644 --- a/backend/windmill-worker/src/ai/types.rs +++ b/backend/windmill-worker/src/ai/types.rs @@ -190,6 +190,9 @@ pub struct ProviderResource { /// Platform for Anthropic API (standard or google_vertex_ai) #[serde(default)] pub platform: AnthropicPlatform, + /// Enable 1M context window for Anthropic + #[serde(alias = "enable_1M_context", default)] + pub enable_1m_context: bool, } #[derive(Deserialize, Debug)] @@ -232,6 +235,10 @@ impl ProviderWithResource { pub fn get_platform(&self) -> &AnthropicPlatform { &self.resource.platform } + + pub fn get_enable_1m_context(&self) -> bool { + self.resource.enable_1m_context + } } /// Token usage information from the AI provider From 700c61243fc8370978380985c4299586ebfac62b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 13:08:25 +0100 Subject: [PATCH 05/93] chore(main): release 1.631.0 (#7898) * chore(main): release 1.631.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 16 ++ backend/Cargo.lock | 142 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 104 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa40577230..cf7a2aa5c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.631.0](https://github.com/windmill-labs/windmill/compare/v1.630.2...v1.631.0) (2026-02-11) + + +### Features + +* **ai:** support 1M context window for Anthropic resources ([#7891](https://github.com/windmill-labs/windmill/issues/7891)) ([f22eb96](https://github.com/windmill-labs/windmill/commit/f22eb964e47defe9922ecdb6ab471dd4ca267952)) +* **uv:** index resolve strategy ([#7885](https://github.com/windmill-labs/windmill/issues/7885)) ([097d928](https://github.com/windmill-labs/windmill/commit/097d9288c58076882f1991e2fb33e4441fe332d3)) + + +### Bug Fixes + +* **frontend:** improve time picker ([#7893](https://github.com/windmill-labs/windmill/issues/7893)) ([31bfccc](https://github.com/windmill-labs/windmill/commit/31bfccc74588af10fc11fbbb0bc4833d65ff6421)) +* otel gracefully handle no native ts ([92c6018](https://github.com/windmill-labs/windmill/commit/92c601860f1e1211aa34838cd08900ba7334a20c)) +* waitJob getJob and streamJob in raw apps ([#7901](https://github.com/windmill-labs/windmill/issues/7901)) ([754b48c](https://github.com/windmill-labs/windmill/commit/754b48cb898dffe196339cea1c1598c9e1765cdc)) +* worker do not apply migrations anymore but wait for servers to do so ([7eb239f](https://github.com/windmill-labs/windmill/commit/7eb239f1e2eb1b71234a8d4265c7c5813e5861ae)) + ## [1.630.2](https://github.com/windmill-labs/windmill/compare/v1.630.1...v1.630.2) (2026-02-11) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 3bc383bf77..5b00c22175 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -7858,9 +7858,9 @@ checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" [[package]] name = "lazy-regex" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c13b6857ade4c8ee05c3c3dc97d2ab5415d691213825b90d3211c425c1f907" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -7869,9 +7869,9 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a95c68db5d41694cea563c86a4ba4dc02141c16ef64814108cb23def4d5438" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" dependencies = [ "proc-macro2", "quote", @@ -14840,9 +14840,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typed-path" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" [[package]] name = "typeid" @@ -15725,7 +15725,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-nats", @@ -15785,7 +15785,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15798,7 +15798,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "argon2", @@ -15934,7 +15934,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15957,7 +15957,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15970,7 +15970,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -15996,7 +15996,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.630.2" +version = "1.631.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16006,7 +16006,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16023,7 +16023,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16046,7 +16046,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16069,7 +16069,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16085,7 +16085,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16105,7 +16105,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16119,7 +16119,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16144,7 +16144,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "flate2", @@ -16160,7 +16160,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16181,7 +16181,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16201,7 +16201,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16230,7 +16230,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16256,7 +16256,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.630.2" +version = "1.631.0" dependencies = [ "lazy_static", "serde", @@ -16268,7 +16268,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.630.2" +version = "1.631.0" dependencies = [ "argon2", "axum 0.7.9", @@ -16291,7 +16291,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16305,7 +16305,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.630.2" +version = "1.631.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16334,7 +16334,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.630.2" +version = "1.631.0" dependencies = [ "chrono", "lazy_static", @@ -16348,7 +16348,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16367,7 +16367,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.630.2" +version = "1.631.0" dependencies = [ "aes-gcm", "anyhow", @@ -16466,7 +16466,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.630.2" +version = "1.631.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16485,7 +16485,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.630.2" +version = "1.631.0" dependencies = [ "regex", "serde", @@ -16500,7 +16500,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16524,7 +16524,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "futures", @@ -16541,7 +16541,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.630.2" +version = "1.631.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16557,7 +16557,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -16578,7 +16578,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -16609,7 +16609,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-oauth2", @@ -16633,7 +16633,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.630.2" +version = "1.631.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16642,7 +16642,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "lazy_static", @@ -16654,7 +16654,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "serde_json", @@ -16666,7 +16666,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "gosyn", @@ -16678,7 +16678,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "lazy_static", @@ -16690,7 +16690,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "serde_json", @@ -16702,7 +16702,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "nu-parser", @@ -16713,7 +16713,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16724,7 +16724,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16737,7 +16737,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-recursion", @@ -16761,7 +16761,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "lazy_static", @@ -16775,7 +16775,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16792,7 +16792,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "lazy_static", @@ -16806,7 +16806,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "lazy_static", @@ -16825,7 +16825,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "serde", @@ -16836,7 +16836,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-recursion", @@ -16873,7 +16873,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "const_format", @@ -16911,7 +16911,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.630.2" +version = "1.631.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -16921,7 +16921,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-recursion", @@ -16950,7 +16950,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -16983,7 +16983,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17003,7 +17003,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17037,7 +17037,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17071,7 +17071,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17094,7 +17094,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17118,7 +17118,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-nats", @@ -17142,7 +17142,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17177,7 +17177,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17205,7 +17205,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-trait", @@ -17228,7 +17228,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "chrono", @@ -17245,7 +17245,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.630.2" +version = "1.631.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 37b367619d..aa843b8dc5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.630.2" +version = "1.631.0" authors.workspace = true edition.workspace = true @@ -71,7 +71,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.630.2" +version = "1.631.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 497fab7c67..04d8ddbd59 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.630.2 + version: 1.631.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index f7c247b0dd..9d232bd7a5 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.630.2"; +export const VERSION = "v1.631.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 84c5dd146e..de140b8240 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -77,7 +77,7 @@ export { // } // }); -export const VERSION = "1.630.2"; +export const VERSION = "1.631.0"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 154e232425..da33c34a5c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.630.2", + "version": "1.631.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.630.2", + "version": "1.631.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index fa074a2a1b..63ddb0d4ea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.630.2", + "version": "1.631.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index d910c9df90..e997d8b52c 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.630.2" -wmill_pg = ">=1.630.2" +wmill = ">=1.631.0" +wmill_pg = ">=1.631.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index da5fe74570..195c928d68 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.630.2 + version: 1.631.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 27e5d3ada8..e0f6d3bdf6 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.630.2' + ModuleVersion = '1.631.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 9a2329635b..260d9a0a94 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.630.2" +version = "1.631.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 6827bfcff3..155c2a9419 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.630.2" +version = "1.631.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 052004ae7b..b473b2ca60 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.630.2", + "version": "1.631.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 79599f7173..9c66d84aac 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.630.2", + "version": "1.631.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index b96f6507e9..74e0a6c96f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.630.2 +1.631.0 From 2815cfae1a5eefb2c553e893dc926e62ad1df528 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 12:40:59 +0000 Subject: [PATCH 06/93] fix: add kafka-gssapi support to ee builds --- backend/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index aa843b8dc5..585892aa91 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -163,7 +163,7 @@ oss_core = [ ce_core = ["oss_core", "private"] ee_core = [ "enterprise", "stripe", "prometheus", "cloud", - "kafka", "sqs_trigger", "nats", "gcp_trigger", + "kafka", "kafka-gssapi", "sqs_trigger", "nats", "gcp_trigger", "jemalloc", "otel" ] ee_server = ["enterprise_saml", "tantivy", "agent_worker_server", "local_reports"] From ff72b810042d0c1cfae38610d3847601f633b652 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 13:45:39 +0100 Subject: [PATCH 07/93] chore(main): release 1.631.1 (#7904) * chore(main): release 1.631.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 + backend/Cargo.lock | 130 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 89 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7a2aa5c0..e27a5ac29e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.631.1](https://github.com/windmill-labs/windmill/compare/v1.631.0...v1.631.1) (2026-02-11) + + +### Bug Fixes + +* add kafka-gssapi support to ee builds ([2815cfa](https://github.com/windmill-labs/windmill/commit/2815cfae1a5eefb2c553e893dc926e62ad1df528)) + ## [1.631.0](https://github.com/windmill-labs/windmill/compare/v1.630.2...v1.631.0) (2026-02-11) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5b00c22175..cc846db981 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15725,7 +15725,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-nats", @@ -15785,7 +15785,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15798,7 +15798,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "argon2", @@ -15934,7 +15934,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15957,7 +15957,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15970,7 +15970,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -15996,7 +15996,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.631.0" +version = "1.631.1" dependencies = [ "reqwest 0.12.28", "serde", @@ -16006,7 +16006,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16023,7 +16023,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16046,7 +16046,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16069,7 +16069,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16085,7 +16085,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16105,7 +16105,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16119,7 +16119,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16144,7 +16144,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "flate2", @@ -16160,7 +16160,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16181,7 +16181,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16201,7 +16201,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16230,7 +16230,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16256,7 +16256,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.631.0" +version = "1.631.1" dependencies = [ "lazy_static", "serde", @@ -16268,7 +16268,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.631.0" +version = "1.631.1" dependencies = [ "argon2", "axum 0.7.9", @@ -16291,7 +16291,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16305,7 +16305,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.631.0" +version = "1.631.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16334,7 +16334,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.631.0" +version = "1.631.1" dependencies = [ "chrono", "lazy_static", @@ -16348,7 +16348,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16367,7 +16367,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.631.0" +version = "1.631.1" dependencies = [ "aes-gcm", "anyhow", @@ -16466,7 +16466,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.631.0" +version = "1.631.1" dependencies = [ "chrono", "itertools 0.14.0", @@ -16485,7 +16485,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.631.0" +version = "1.631.1" dependencies = [ "regex", "serde", @@ -16500,7 +16500,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16524,7 +16524,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "futures", @@ -16541,7 +16541,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.631.0" +version = "1.631.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16557,7 +16557,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -16578,7 +16578,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -16609,7 +16609,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-oauth2", @@ -16633,7 +16633,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.631.0" +version = "1.631.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -16642,7 +16642,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "lazy_static", @@ -16654,7 +16654,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "serde_json", @@ -16666,7 +16666,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "gosyn", @@ -16678,7 +16678,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "lazy_static", @@ -16690,7 +16690,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "serde_json", @@ -16702,7 +16702,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "nu-parser", @@ -16713,7 +16713,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16724,7 +16724,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16737,7 +16737,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-recursion", @@ -16761,7 +16761,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "lazy_static", @@ -16775,7 +16775,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16792,7 +16792,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "lazy_static", @@ -16806,7 +16806,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "lazy_static", @@ -16825,7 +16825,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "serde", @@ -16836,7 +16836,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-recursion", @@ -16873,7 +16873,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "const_format", @@ -16911,7 +16911,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.631.0" +version = "1.631.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -16921,7 +16921,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-recursion", @@ -16950,7 +16950,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -16983,7 +16983,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17003,7 +17003,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17037,7 +17037,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17071,7 +17071,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17094,7 +17094,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17118,7 +17118,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-nats", @@ -17142,7 +17142,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17177,7 +17177,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17205,7 +17205,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-trait", @@ -17228,7 +17228,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "chrono", @@ -17245,7 +17245,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.631.0" +version = "1.631.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 585892aa91..1248c19af5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.631.0" +version = "1.631.1" authors.workspace = true edition.workspace = true @@ -71,7 +71,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.631.0" +version = "1.631.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 04d8ddbd59..6e7752a2f5 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.631.0 + version: 1.631.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 9d232bd7a5..b02ced7283 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.631.0"; +export const VERSION = "v1.631.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index de140b8240..3274c35f2b 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -77,7 +77,7 @@ export { // } // }); -export const VERSION = "1.631.0"; +export const VERSION = "1.631.1"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index da33c34a5c..85513fbbb3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.631.0", + "version": "1.631.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.631.0", + "version": "1.631.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 63ddb0d4ea..045d4e8379 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.631.0", + "version": "1.631.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index e997d8b52c..fe626dda2d 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.631.0" -wmill_pg = ">=1.631.0" +wmill = ">=1.631.1" +wmill_pg = ">=1.631.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 195c928d68..23f68cfe94 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.631.0 + version: 1.631.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index e0f6d3bdf6..132e472215 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.631.0' + ModuleVersion = '1.631.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 260d9a0a94..1179e9f656 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.631.0" +version = "1.631.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 155c2a9419..6c4f58d66f 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.631.0" +version = "1.631.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index b473b2ca60..fbdb3b4697 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.631.0", + "version": "1.631.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 9c66d84aac..0990b945cd 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.631.0", + "version": "1.631.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 74e0a6c96f..0f12a0973d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.631.0 +1.631.1 From 900c76ccad09f58fc6adcfa8151db780249617f6 Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:13:36 +0100 Subject: [PATCH 08/93] fix: nix flake libz.so for deno_core (#7905) --- flake.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flake.nix b/flake.nix index 0c5faec51f..fc01be4166 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,7 @@ libxslt.dev libclang.dev curl.dev + zlib.dev libffi # For deno_ffi libtool nodejs @@ -324,6 +325,9 @@ # LD_LIBRARY_PATH set in shellHook with a wrapper to avoid leaking into git/ssh # LD_LIBRARY_PATH = "${pkgs.gcc.lib}/lib"; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ + pkgs.zlib + ]; # Set C flags for Rust's bindgen program. Unlike ordinary C # compilation, bindgen does not invoke $CC directly. Instead it From 5effb87a36793e20d17e678619091ef458fe8f0a Mon Sep 17 00:00:00 2001 From: Devdatta Talele <50290838+devdattatalele@users.noreply.github.com> Date: Wed, 11 Feb 2026 18:56:08 +0530 Subject: [PATCH 09/93] fix: strip unsupported schema fields for Google AI (#7894) Closes #7759 --- backend/windmill-worker/src/ai/types.rs | 136 +++++++++++++++++++++++- 1 file changed, 134 insertions(+), 2 deletions(-) diff --git a/backend/windmill-worker/src/ai/types.rs b/backend/windmill-worker/src/ai/types.rs index 56e1571a6b..0283c52a2f 100644 --- a/backend/windmill-worker/src/ai/types.rs +++ b/backend/windmill-worker/src/ai/types.rs @@ -784,10 +784,14 @@ impl OpenAPISchema { } /// Sanitizes this schema for Google AI's API by removing unsupported fields. - /// Google's Gemini API does not accept JSON Schema metadata fields like $schema. + /// See https://github.com/windmill-labs/windmill/issues/7759 pub fn sanitize_for_google(&mut self) { - // Remove $schema field - not supported by Google AI self.schema_url = None; + self.default = None; + self.exclusive_minimum = None; + self.exclusive_maximum = None; + self.r#const = None; + self.multiple_of = None; // Recursively sanitize nested schemas if let Some(ref mut items) = self.items { @@ -1493,6 +1497,8 @@ mod tests { schema_url: Some("http://json-schema.org/draft-07/schema#".to_string()), title: Some("Test Schema".to_string()), description: Some("A test schema".to_string()), + minimum: Some(1.0), + maximum: Some(100.0), properties: Some( vec![("name".to_string(), Box::new(string_schema()))] .into_iter() @@ -1512,5 +1518,131 @@ mod tests { assert!(schema.properties.is_some()); assert!(schema.required.is_some()); assert!(matches!(&schema.r#type, Some(SchemaType::Single(t)) if t == "object")); + assert_eq!(schema.minimum, Some(1.0)); + assert_eq!(schema.maximum, Some(100.0)); + } + + #[test] + fn test_sanitize_for_google_removes_default() { + let mut schema = OpenAPISchema { + r#type: Some(SchemaType::Single("string".to_string())), + default: Some(serde_json::Value::String("hello".to_string())), + ..Default::default() + }; + + schema.sanitize_for_google(); + + assert!(schema.default.is_none(), "default should be removed"); + assert!(matches!(&schema.r#type, Some(SchemaType::Single(t)) if t == "string")); + } + + #[test] + fn test_sanitize_for_google_removes_exclusive_minimum() { + let mut schema = OpenAPISchema { + r#type: Some(SchemaType::Single("number".to_string())), + exclusive_minimum: Some(0.0), + ..Default::default() + }; + + schema.sanitize_for_google(); + + assert!( + schema.exclusive_minimum.is_none(), + "exclusiveMinimum should be removed" + ); + } + + #[test] + fn test_sanitize_for_google_removes_exclusive_maximum() { + let mut schema = OpenAPISchema { + r#type: Some(SchemaType::Single("number".to_string())), + exclusive_maximum: Some(100.0), + ..Default::default() + }; + + schema.sanitize_for_google(); + + assert!( + schema.exclusive_maximum.is_none(), + "exclusiveMaximum should be removed" + ); + } + + #[test] + fn test_sanitize_for_google_removes_const() { + let mut schema = OpenAPISchema { + r#type: Some(SchemaType::Single("string".to_string())), + r#const: Some(serde_json::Value::String("fixed".to_string())), + ..Default::default() + }; + + schema.sanitize_for_google(); + + assert!(schema.r#const.is_none(), "const should be removed"); + } + + #[test] + fn test_sanitize_for_google_removes_multiple_of() { + let mut schema = OpenAPISchema { + r#type: Some(SchemaType::Single("integer".to_string())), + multiple_of: Some(5.0), + ..Default::default() + }; + + schema.sanitize_for_google(); + + assert!( + schema.multiple_of.is_none(), + "multipleOf should be removed" + ); + } + + #[test] + fn test_sanitize_for_google_removes_unsupported_fields_recursively() { + let nested = OpenAPISchema { + r#type: Some(SchemaType::Single("number".to_string())), + default: Some(serde_json::json!(42)), + exclusive_minimum: Some(0.0), + exclusive_maximum: Some(100.0), + multiple_of: Some(2.0), + r#const: Some(serde_json::json!(10)), + ..Default::default() + }; + + let mut schema = OpenAPISchema { + r#type: Some(SchemaType::Single("object".to_string())), + schema_url: Some("http://json-schema.org/draft-07/schema#".to_string()), + default: Some(serde_json::json!({})), + properties: Some( + vec![("value".to_string(), Box::new(nested))] + .into_iter() + .collect(), + ), + ..Default::default() + }; + + schema.sanitize_for_google(); + + assert!(schema.schema_url.is_none()); + assert!(schema.default.is_none()); + + let value_prop = schema.properties.as_ref().unwrap().get("value").unwrap(); + assert!(value_prop.default.is_none(), "nested default should be removed"); + assert!( + value_prop.exclusive_minimum.is_none(), + "nested exclusiveMinimum should be removed" + ); + assert!( + value_prop.exclusive_maximum.is_none(), + "nested exclusiveMaximum should be removed" + ); + assert!( + value_prop.multiple_of.is_none(), + "nested multipleOf should be removed" + ); + assert!(value_prop.r#const.is_none(), "nested const should be removed"); + + assert!(schema.properties.is_some()); + assert!(matches!(&schema.r#type, Some(SchemaType::Single(t)) if t == "object")); } } From b9e0ccaefdb95a59b53ccdeca31ebaf154a72d65 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 13:26:57 +0000 Subject: [PATCH 10/93] chore: split api-scripts and api-flows --- backend/Cargo.lock | 21 +++++++++++++ backend/Cargo.toml | 8 +++-- backend/windmill-api-flows/Cargo.toml | 31 +++++++++++++++++++ .../src/flows.rs | 0 backend/windmill-api-flows/src/lib.rs | 9 ++++++ backend/windmill-api-scripts/src/lib.rs | 1 - backend/windmill-api/Cargo.toml | 3 +- backend/windmill-api/src/flows.rs | 6 ++-- 8 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 backend/windmill-api-flows/Cargo.toml rename backend/{windmill-api-scripts => windmill-api-flows}/src/flows.rs (100%) create mode 100644 backend/windmill-api-flows/src/lib.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index cc846db981..37204316ca 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15890,6 +15890,7 @@ dependencies = [ "windmill-api-debug", "windmill-api-embeddings", "windmill-api-flow-conversations", + "windmill-api-flows", "windmill-api-groups", "windmill-api-inputs", "windmill-api-jobs", @@ -16083,6 +16084,26 @@ dependencies = [ "windmill-common", ] +[[package]] +name = "windmill-api-flows" +version = "1.631.1" +dependencies = [ + "axum 0.7.9", + "chrono", + "hyper 1.8.1", + "serde", + "serde_json", + "sql-builder", + "sqlx", + "tracing", + "windmill-api-auth", + "windmill-audit", + "windmill-common", + "windmill-dep-map", + "windmill-git-sync", + "windmill-queue", +] + [[package]] name = "windmill-api-groups" version = "1.631.1" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 1248c19af5..b8ae9f8c1d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,6 +9,7 @@ resolver = "2" members = [ "./windmill-api", "./windmill-api-scripts", + "./windmill-api-flows", "./windmill-api-users", "./windmill-api-workspaces", "./windmill-api-groups", @@ -163,7 +164,7 @@ oss_core = [ ce_core = ["oss_core", "private"] ee_core = [ "enterprise", "stripe", "prometheus", "cloud", - "kafka", "kafka-gssapi", "sqs_trigger", "nats", "gcp_trigger", + "kafka", "sqs_trigger", "nats", "gcp_trigger", "jemalloc", "otel" ] ee_server = ["enterprise_saml", "tantivy", "agent_worker_server", "local_reports"] @@ -172,8 +173,8 @@ oss = ["oss_core", "all_languages", "no_auth"] ce_rpi = ["ce_core", "all_languages"] ce = ["ce_rpi", "jemalloc", "dind", "agent_worker_server"] # Edition meta-features: EE variants -ee = ["ce", "ee_core", "ee_server"] -ee_rhel = ["ce_core", "ee_core", "all_languages"] +ee = ["ce", "ee_core", "ee_server", "kafka-gssapi"] +ee_rhel = ["ce_core", "ee_core", "kafka-gssapi", "all_languages"] ee_windows = ["ce_core", "ee_core", "all_languages_windows"] all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embedding", "parquet", "prometheus", "flow_testing", "openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "kafka-gssapi", "nats", "otel", "dind", "websocket", "http_trigger", @@ -267,6 +268,7 @@ windmill-oauth = {path = "./windmill-oauth"} windmill-macros = {path = "./windmill-macros"} windmill-api-auth = { path = "./windmill-api-auth" } windmill-api-scripts = { path = "./windmill-api-scripts" } +windmill-api-flows = { path = "./windmill-api-flows" } windmill-api-users = { path = "./windmill-api-users" } windmill-api-workspaces = { path = "./windmill-api-workspaces" } windmill-api-groups = { path = "./windmill-api-groups" } diff --git a/backend/windmill-api-flows/Cargo.toml b/backend/windmill-api-flows/Cargo.toml new file mode 100644 index 0000000000..5407e38068 --- /dev/null +++ b/backend/windmill-api-flows/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "windmill-api-flows" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_api_flows" +path = "src/lib.rs" + +[features] +default = [] +enterprise = ["windmill-common/enterprise"] +private = ["windmill-common/private"] + +[dependencies] +windmill-common = { workspace = true, default-features = false } +windmill-api-auth.workspace = true +windmill-queue.workspace = true +windmill-audit.workspace = true +windmill-git-sync.workspace = true +windmill-dep-map.workspace = true + +axum.workspace = true +hyper.workspace = true +serde.workspace = true +serde_json.workspace = true +sql-builder.workspace = true +sqlx.workspace = true +tracing.workspace = true +chrono.workspace = true diff --git a/backend/windmill-api-scripts/src/flows.rs b/backend/windmill-api-flows/src/flows.rs similarity index 100% rename from backend/windmill-api-scripts/src/flows.rs rename to backend/windmill-api-flows/src/flows.rs diff --git a/backend/windmill-api-flows/src/lib.rs b/backend/windmill-api-flows/src/lib.rs new file mode 100644 index 0000000000..3a8530f178 --- /dev/null +++ b/backend/windmill-api-flows/src/lib.rs @@ -0,0 +1,9 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2022 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +pub mod flows; diff --git a/backend/windmill-api-scripts/src/lib.rs b/backend/windmill-api-scripts/src/lib.rs index aab68a4d52..9bc8318fc4 100644 --- a/backend/windmill-api-scripts/src/lib.rs +++ b/backend/windmill-api-scripts/src/lib.rs @@ -6,5 +6,4 @@ * LICENSE-AGPL for a copy of the license. */ -pub mod flows; pub mod scripts; diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index bb08985003..6d63b62cbf 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [features] default = [] private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"] -enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] +enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] stripe = [] inline_preview = ["dep:windmill-worker", "windmill-api-configs/inline_preview"] agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"] @@ -48,6 +48,7 @@ quickjs = ["windmill-jseval/quickjs"] windmill-mcp = { workspace = true, optional = true } windmill-api-auth.workspace = true windmill-api-scripts.workspace = true +windmill-api-flows.workspace = true windmill-api-users.workspace = true windmill-api-workspaces.workspace = true windmill-api-groups.workspace = true diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index cc1c3e97c0..c6a09a9e6a 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -6,8 +6,8 @@ * LICENSE-AGPL for a copy of the license. */ -// Re-export everything from windmill-api-scripts -pub use windmill_api_scripts::flows::*; +// Re-export everything from windmill-api-flows +pub use windmill_api_flows::flows::*; use crate::triggers::{get_triggers_count_internal, TriggersCount}; use axum::{ @@ -20,7 +20,7 @@ use windmill_common::{error::JsonResult, utils::StripPath, DB}; /// Wraps the subcrate's workspaced_service with the trigger count route /// that depends on windmill-api internals. pub fn workspaced_service() -> Router { - windmill_api_scripts::flows::workspaced_service() + windmill_api_flows::flows::workspaced_service() .route("/get_triggers_count/*path", get(get_triggers_count)) } From ea52a8b8cee786e84a28f4620def73ed6c2b006e Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 14:04:54 +0000 Subject: [PATCH 11/93] refactor: move integration tests to subcrates to reduce recompilation Co-Authored-By: Claude Opus 4.6 --- backend/Cargo.lock | 44 ++++ backend/Cargo.toml | 6 +- backend/tests/agent_workers.rs | 4 +- backend/tests/bun_jobs.rs | 5 +- backend/tests/dependency_map.rs | 3 +- backend/tests/error_handler.rs | 3 +- backend/tests/flow_engine_parity.rs | 3 +- backend/tests/job_payload.rs | 4 +- backend/tests/list_jobs.rs | 3 +- backend/tests/nativets_jobs.rs | 5 +- backend/tests/nativets_stress.rs | 5 +- backend/tests/python_jobs.rs | 3 +- backend/tests/retry.rs | 4 +- backend/tests/success_handler.rs | 3 +- backend/tests/suspend_resume.rs | 4 +- backend/tests/worker.rs | 3 +- backend/tests/workspace_dependencies.rs | 7 +- .../windmill-api-integration-tests/Cargo.toml | 30 +++ .../windmill-api-integration-tests/src/lib.rs | 2 + .../tests/apps.rs | 5 +- .../tests/drafts.rs | 5 +- .../tests/fixtures/base.sql | 146 +++++++++++ .../tests/fixtures/permissions_test.sql | 238 ++++++++++++++++++ .../tests/fixtures/resources_test.sql | 69 +++++ .../tests/fixtures/variables_test.sql | 10 + .../tests/flows.rs | 5 +- .../tests/folders.rs | 5 +- .../tests/groups.rs | 5 +- .../tests/permissions.rs | 11 +- .../tests/protection_rules.rs | 5 +- .../tests/resources.rs | 7 +- .../tests/schedules.rs | 5 +- .../tests/scripts.rs | 5 +- .../tests/trigger_e2e.rs | 17 +- .../tests/triggers.rs | 73 +++--- .../tests/users.rs | 5 +- .../tests/variables.rs | 5 +- .../tests/workspace_comparison.rs | 5 +- .../tests/workspaces.rs | 5 +- .../windmill-common/tests/fixtures/base.sql | 146 +++++++++++ .../fixtures/instance_group_auto_add.sql | 76 ++++++ .../tests/fixtures/secret_backend.sql | 31 +++ .../tests/instance_group_auto_add.rs | 20 +- .../tests/notify_events.rs | 50 ++-- .../tests/secret_backend_integration.rs | 18 +- .../tests/secret_backend_migration.rs | 12 +- .../windmill-queue/tests/fixtures/base.sql | 146 +++++++++++ .../tests/fixtures/schedule_push.sql | 84 +++++++ .../tests/job_cleanup_test.rs | 0 .../tests/schedule_push.rs | 85 ++++--- backend/windmill-test-utils/Cargo.toml | 37 +++ .../mod.rs => windmill-test-utils/src/lib.rs} | 1 - 52 files changed, 1248 insertions(+), 230 deletions(-) create mode 100644 backend/windmill-api-integration-tests/Cargo.toml create mode 100644 backend/windmill-api-integration-tests/src/lib.rs rename backend/{ => windmill-api-integration-tests}/tests/apps.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/drafts.rs (97%) create mode 100644 backend/windmill-api-integration-tests/tests/fixtures/base.sql create mode 100644 backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql create mode 100644 backend/windmill-api-integration-tests/tests/fixtures/resources_test.sql create mode 100644 backend/windmill-api-integration-tests/tests/fixtures/variables_test.sql rename backend/{ => windmill-api-integration-tests}/tests/flows.rs (99%) rename backend/{ => windmill-api-integration-tests}/tests/folders.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/groups.rs (99%) rename backend/{ => windmill-api-integration-tests}/tests/permissions.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/protection_rules.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/resources.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/schedules.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/scripts.rs (99%) rename backend/{ => windmill-api-integration-tests}/tests/trigger_e2e.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/triggers.rs (95%) rename backend/{ => windmill-api-integration-tests}/tests/users.rs (99%) rename backend/{ => windmill-api-integration-tests}/tests/variables.rs (98%) rename backend/{ => windmill-api-integration-tests}/tests/workspace_comparison.rs (99%) rename backend/{ => windmill-api-integration-tests}/tests/workspaces.rs (99%) create mode 100644 backend/windmill-common/tests/fixtures/base.sql create mode 100644 backend/windmill-common/tests/fixtures/instance_group_auto_add.sql create mode 100644 backend/windmill-common/tests/fixtures/secret_backend.sql rename backend/{ => windmill-common}/tests/instance_group_auto_add.rs (96%) rename backend/{ => windmill-common}/tests/notify_events.rs (95%) rename backend/{ => windmill-common}/tests/secret_backend_integration.rs (96%) rename backend/{ => windmill-common}/tests/secret_backend_migration.rs (96%) create mode 100644 backend/windmill-queue/tests/fixtures/base.sql create mode 100644 backend/windmill-queue/tests/fixtures/schedule_push.sql rename backend/{ => windmill-queue}/tests/job_cleanup_test.rs (100%) rename backend/{ => windmill-queue}/tests/schedule_push.rs (95%) create mode 100644 backend/windmill-test-utils/Cargo.toml rename backend/{tests/common/mod.rs => windmill-test-utils/src/lib.rs} (99%) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 37204316ca..bef3fe26f7 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15778,6 +15778,7 @@ dependencies = [ "windmill-indexer", "windmill-queue", "windmill-runtime-nativets", + "windmill-test-utils", "windmill-worker", "windows-service", "windows-sys 0.52.0", @@ -16138,6 +16139,26 @@ dependencies = [ "windmill-common", ] +[[package]] +name = "windmill-api-integration-tests" +version = "1.631.1" +dependencies = [ + "anyhow", + "async-nats", + "rand 0.9.0", + "rdkafka", + "reqwest 0.13.1", + "rumqttc", + "serde", + "serde_json", + "sqlx", + "tokio", + "uuid", + "windmill-api-client", + "windmill-common", + "windmill-test-utils", +] + [[package]] name = "windmill-api-jobs" version = "1.631.1" @@ -16969,6 +16990,29 @@ dependencies = [ "windmill-oauth", ] +[[package]] +name = "windmill-test-utils" +version = "1.631.1" +dependencies = [ + "anyhow", + "axum 0.7.9", + "chrono", + "futures", + "serde", + "serde_json", + "sqlx", + "tokio", + "tracing", + "uuid", + "windmill-api", + "windmill-api-agent-workers", + "windmill-api-client", + "windmill-common", + "windmill-queue", + "windmill-runtime-nativets", + "windmill-worker", +] + [[package]] name = "windmill-trigger" version = "1.631.1" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b8ae9f8c1d..9c22b21604 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -67,7 +67,9 @@ members = [ "./parsers/windmill-parser-py", "./parsers/windmill-parser-py-imports", "./parsers/windmill-sql-datatype-parser-wasm", - "./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu" + "./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu", + "./windmill-test-utils", + "./windmill-api-integration-tests", ] exclude = ["./windmill-duckdb-ffi-internal"] @@ -240,6 +242,7 @@ serde_json.workspace = true reqwest.workspace = true windmill-queue = { workspace = true, features = ["failpoints"] } windmill-dep-map.workspace = true +windmill-test-utils.workspace = true axum.workspace = true serde.workspace = true windmill-api-client.workspace = true @@ -317,6 +320,7 @@ windmill-parser-php = { path = "./parsers/windmill-parser-php" } windmill-jseval = { path = "./windmill-jseval" } windmill-runtime-nativets = { path = "./windmill-runtime-nativets" } windmill-api-client = { path = "./windmill-api-client" } +windmill-test-utils = { path = "./windmill-test-utils" } reqwest-retry = "^0" reqwest-middleware = { version = "^0", features = ["json"] } diff --git a/backend/tests/agent_workers.rs b/backend/tests/agent_workers.rs index 26c4003212..b6414dc24e 100644 --- a/backend/tests/agent_workers.rs +++ b/backend/tests/agent_workers.rs @@ -1,8 +1,6 @@ #![cfg(all(feature = "private", feature = "agent_worker_server"))] -mod common; - -use common::*; +use windmill_test_utils::*; use serde_json::json; use sqlx::{Pool, Postgres}; use windmill_common::{ diff --git a/backend/tests/bun_jobs.rs b/backend/tests/bun_jobs.rs index cf3ce4d350..534d530095 100644 --- a/backend/tests/bun_jobs.rs +++ b/backend/tests/bun_jobs.rs @@ -1,5 +1,4 @@ -mod common; -use crate::common::*; +use windmill_test_utils::*; use sqlx::postgres::Postgres; use sqlx::Pool; use windmill_common::jobs::{JobPayload, RawCode}; @@ -860,7 +859,7 @@ export function main() { // ============================================================================ mod dedicated_worker_protocol { - use crate::common::{parse_dedicated_worker_line, DedicatedWorkerResult}; + use windmill_test_utils::{parse_dedicated_worker_line, DedicatedWorkerResult}; use std::io::{BufRead, BufReader, Write}; use std::process::{Command, Stdio}; use windmill_worker::{ diff --git a/backend/tests/dependency_map.rs b/backend/tests/dependency_map.rs index d217b710d6..9113e8f165 100644 --- a/backend/tests/dependency_map.rs +++ b/backend/tests/dependency_map.rs @@ -2,8 +2,7 @@ use sqlx::{Pool, Postgres}; use tokio_stream::StreamExt; use windmill_api_client::types::NewScript; -mod common; -use common::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer}; +use windmill_test_utils::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer}; mod dependency_map { use super::*; diff --git a/backend/tests/error_handler.rs b/backend/tests/error_handler.rs index cad9336ab7..b117cbfba3 100644 --- a/backend/tests/error_handler.rs +++ b/backend/tests/error_handler.rs @@ -1,7 +1,6 @@ use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; /// Test that workspace error handler can be set and removed via database operations #[cfg(feature = "deno_core")] diff --git a/backend/tests/flow_engine_parity.rs b/backend/tests/flow_engine_parity.rs index 875897094e..58b76dc50d 100644 --- a/backend/tests/flow_engine_parity.rs +++ b/backend/tests/flow_engine_parity.rs @@ -25,8 +25,7 @@ use windmill_common::{ scripts::ScriptLang, }; -mod common; -use common::*; +use windmill_test_utils::*; /// Helper to create a FlowModule with default fields fn flow_module(id: &str, value: FlowModuleValue) -> FlowModule { diff --git a/backend/tests/job_payload.rs b/backend/tests/job_payload.rs index 2321db5bcf..87253807be 100644 --- a/backend/tests/job_payload.rs +++ b/backend/tests/job_payload.rs @@ -1,5 +1,3 @@ -mod common; - mod job_payload { use serde_json::json; use sqlx::{Pool, Postgres}; @@ -8,7 +6,7 @@ mod job_payload { use windmill_common::scripts::{ScriptHash, ScriptLang}; use windmill_common::flow_status::RestartedFrom; - use crate::common::*; + use windmill_test_utils::*; use windmill_common::min_version::{ MIN_VERSION, MIN_VERSION_IS_AT_LEAST_1_427, MIN_VERSION_IS_AT_LEAST_1_432, MIN_VERSION_IS_AT_LEAST_1_440, diff --git a/backend/tests/list_jobs.rs b/backend/tests/list_jobs.rs index a3d87385f9..65451bcbb7 100644 --- a/backend/tests/list_jobs.rs +++ b/backend/tests/list_jobs.rs @@ -7,8 +7,7 @@ use windmill_common::{ scripts::ScriptLang, }; -mod common; -use common::*; +use windmill_test_utils::*; #[derive(Debug, Deserialize)] struct ListJobsResponse { diff --git a/backend/tests/nativets_jobs.rs b/backend/tests/nativets_jobs.rs index c654882589..eaf3a96652 100644 --- a/backend/tests/nativets_jobs.rs +++ b/backend/tests/nativets_jobs.rs @@ -14,10 +14,7 @@ */ #[cfg(feature = "deno_core")] -mod common; - -#[cfg(feature = "deno_core")] -use common::*; +use windmill_test_utils::*; #[cfg(feature = "deno_core")] use futures::StreamExt; diff --git a/backend/tests/nativets_stress.rs b/backend/tests/nativets_stress.rs index cf3710d86c..9717403830 100644 --- a/backend/tests/nativets_stress.rs +++ b/backend/tests/nativets_stress.rs @@ -11,10 +11,7 @@ */ #[cfg(feature = "deno_core")] -mod common; - -#[cfg(feature = "deno_core")] -use common::*; +use windmill_test_utils::*; #[cfg(feature = "deno_core")] use std::time::Instant; diff --git a/backend/tests/python_jobs.rs b/backend/tests/python_jobs.rs index 47a554afd5..dd494de007 100644 --- a/backend/tests/python_jobs.rs +++ b/backend/tests/python_jobs.rs @@ -1,5 +1,4 @@ -mod common; -use crate::common::*; +use windmill_test_utils::*; use sqlx::postgres::Postgres; use sqlx::Pool; use windmill_common::scripts::ScriptLang; diff --git a/backend/tests/retry.rs b/backend/tests/retry.rs index 60b81573db..cb77e4546b 100644 --- a/backend/tests/retry.rs +++ b/backend/tests/retry.rs @@ -1,8 +1,6 @@ -mod common; - #[cfg(feature = "deno_core")] mod retry { - use crate::common::*; + use windmill_test_utils::*; use serde_json::json; use sqlx::{Pool, Postgres}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; diff --git a/backend/tests/success_handler.rs b/backend/tests/success_handler.rs index e3c733100e..2f580e4dcb 100644 --- a/backend/tests/success_handler.rs +++ b/backend/tests/success_handler.rs @@ -1,7 +1,6 @@ use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; /// Test that the workspace success handler cache works correctly with 60s TTL #[cfg(feature = "deno_core")] diff --git a/backend/tests/suspend_resume.rs b/backend/tests/suspend_resume.rs index a3e4cc456f..f2347af57a 100644 --- a/backend/tests/suspend_resume.rs +++ b/backend/tests/suspend_resume.rs @@ -1,11 +1,9 @@ -mod common; - mod suspend_resume { #[cfg(feature = "deno_core")] use serde_json::json; #[cfg(feature = "deno_core")] - use crate::common::*; + use windmill_test_utils::*; #[cfg(feature = "deno_core")] use futures::{Stream, StreamExt}; diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index f98e1e3ad7..d084a0177c 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -25,8 +25,7 @@ use windmill_common::{ jobs::{JobPayload, RawCode}, scripts::ScriptLang, }; -mod common; -use common::*; +use windmill_test_utils::*; #[cfg(feature = "enterprise")] use futures::StreamExt; diff --git a/backend/tests/workspace_dependencies.rs b/backend/tests/workspace_dependencies.rs index 124e727e91..d8e705d0c3 100644 --- a/backend/tests/workspace_dependencies.rs +++ b/backend/tests/workspace_dependencies.rs @@ -1,9 +1,8 @@ -mod common; mod workspace_dependencies { - use crate::common::in_test_worker; - use crate::common::init_client; - use crate::common::listen_for_completed_jobs; + use windmill_test_utils::in_test_worker; + use windmill_test_utils::init_client; + use windmill_test_utils::listen_for_completed_jobs; use sqlx::{Pool, Postgres}; use tokio_stream::StreamExt; use windmill_common::scripts::ScriptLang; diff --git a/backend/windmill-api-integration-tests/Cargo.toml b/backend/windmill-api-integration-tests/Cargo.toml new file mode 100644 index 0000000000..8caa178d3d --- /dev/null +++ b/backend/windmill-api-integration-tests/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "windmill-api-integration-tests" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_api_integration_tests" +path = "src/lib.rs" + +[features] +default = [] +private = ["windmill-test-utils/private"] +enterprise = ["windmill-test-utils/enterprise"] + +[dependencies] +windmill-test-utils.workspace = true +windmill-api-client.workspace = true +windmill-common = { workspace = true, default-features = false } +sqlx.workspace = true +serde_json.workspace = true +serde.workspace = true +reqwest.workspace = true +tokio.workspace = true +anyhow.workspace = true +uuid.workspace = true +rand.workspace = true +rumqttc.workspace = true +rdkafka.workspace = true +async-nats.workspace = true diff --git a/backend/windmill-api-integration-tests/src/lib.rs b/backend/windmill-api-integration-tests/src/lib.rs new file mode 100644 index 0000000000..00682b90bc --- /dev/null +++ b/backend/windmill-api-integration-tests/src/lib.rs @@ -0,0 +1,2 @@ +// This crate exists only to host integration tests. +// See tests/ directory for the actual test files. diff --git a/backend/tests/apps.rs b/backend/windmill-api-integration-tests/tests/apps.rs similarity index 98% rename from backend/tests/apps.rs rename to backend/windmill-api-integration-tests/tests/apps.rs index 869858a265..55c85711ae 100644 --- a/backend/tests/apps.rs +++ b/backend/windmill-api-integration-tests/tests/apps.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn app_url(port: u16, endpoint: &str, path: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/apps/{endpoint}/{path}") @@ -40,7 +39,7 @@ fn new_app(path: &str, summary: &str) -> serde_json::Value { }) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_app_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/drafts.rs b/backend/windmill-api-integration-tests/tests/drafts.rs similarity index 97% rename from backend/tests/drafts.rs rename to backend/windmill-api-integration-tests/tests/drafts.rs index e479d0b97c..d86c91b6a1 100644 --- a/backend/tests/drafts.rs +++ b/backend/windmill-api-integration-tests/tests/drafts.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn client() -> reqwest::Client { reqwest::Client::new() @@ -12,7 +11,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder { builder.header("Authorization", "Bearer SECRET_TOKEN") } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_draft_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/windmill-api-integration-tests/tests/fixtures/base.sql b/backend/windmill-api-integration-tests/tests/fixtures/base.sql new file mode 100644 index 0000000000..7db9918fba --- /dev/null +++ b/backend/windmill-api-integration-tests/tests/fixtures/base.sql @@ -0,0 +1,146 @@ +-- used for backend automated testing +-- https://docs.rs/sqlx/latest/sqlx/attr.test.html + +INSERT INTO workspace + (id, name, owner) + VALUES ('test-workspace', 'test-workspace', 'test-user'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin'); + +INSERT INTO workspace_key(workspace_id, kind, key) VALUES + ('test-workspace', 'cloud', 'test-key'); + + +INSERT INTO workspace_settings (workspace_id) VALUES + ('test-workspace'); + +INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES + ('test-workspace', 'all', 'All users', '{}'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username) + VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) + VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) + VALUES ('test3@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 3'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test3@windmill.dev', 'test-user-3', false, 'User'); + +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true); +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false); +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_3', 'test3@windmill.dev', 'test token 3', false); + +GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin; +GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user; + +CREATE FUNCTION "notify_insert_on_completed_job" () +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('completed', NEW.id::text); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + + CREATE TRIGGER "notify_insert_on_completed_job" + AFTER INSERT ON "v2_job_completed" + FOR EACH ROW +EXECUTE FUNCTION "notify_insert_on_completed_job" (); + + +CREATE FUNCTION "notify_queue" () +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('queued', NEW.id::text); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + + CREATE TRIGGER "notify_queue_after_insert" + AFTER INSERT ON "v2_job_queue" + FOR EACH ROW +EXECUTE FUNCTION "notify_queue" (); + + CREATE TRIGGER "notify_queue_after_flow_status_update" + AFTER UPDATE ON "v2_job_status" + FOR EACH ROW + WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status) +EXECUTE FUNCTION "notify_queue" (); + +-- Apply phase 4: +DROP FUNCTION IF EXISTS v2_job_after_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; + +DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE; + +ALTER TABLE v2_job_queue + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __last_ping CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __flow_status CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __same_worker CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __pre_run_error CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __mem_peak CASCADE, + DROP COLUMN IF EXISTS __root_job CASCADE, + DROP COLUMN IF EXISTS __leaf_jobs CASCADE, + DROP COLUMN IF EXISTS __concurrent_limit CASCADE, + DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE, + DROP COLUMN IF EXISTS __timeout CASCADE, + DROP COLUMN IF EXISTS __flow_step_id CASCADE, + DROP COLUMN IF EXISTS __cache_ttl CASCADE; + +LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; +ALTER TABLE v2_job_completed + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __created_at CASCADE, + DROP COLUMN IF EXISTS __success CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __is_skipped CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __tag CASCADE, + DROP COLUMN IF EXISTS __priority CASCADE; diff --git a/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql b/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql new file mode 100644 index 0000000000..a9006f5233 --- /dev/null +++ b/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql @@ -0,0 +1,238 @@ +-- Permissions test fixture +-- This sets up a comprehensive permission testing scenario + +-- ============================================ +-- USERS +-- ============================================ + +-- Admin user (may already exist from base.sql, use ON CONFLICT) +INSERT INTO usr (workspace_id, email, username, is_admin, role) +VALUES ('test-workspace', 'admin@windmill.dev', 'admin', true, 'Admin') +ON CONFLICT (workspace_id, username) DO NOTHING; + +-- Regular users (non-admin) +INSERT INTO usr (workspace_id, email, username, is_admin, role) +VALUES + ('test-workspace', 'alice@windmill.dev', 'alice', false, 'Developer'), + ('test-workspace', 'bob@windmill.dev', 'bob', false, 'Developer'), + ('test-workspace', 'charlie@windmill.dev', 'charlie', false, 'Developer'); + +-- Operator user (can execute but cannot create/update scripts, flows, apps) +INSERT INTO usr (workspace_id, email, username, is_admin, operator, role) +VALUES + ('test-workspace', 'operator@windmill.dev', 'operator', false, true, 'Operator'); + +-- Add users to password table (use ON CONFLICT since admin may exist from base.sql) +INSERT INTO password (email, password_hash, login_type, super_admin, verified, name) +VALUES + ('admin@windmill.dev', 'dummy_hash', 'password', false, true, 'Admin User'), + ('alice@windmill.dev', 'dummy_hash', 'password', false, true, 'Alice'), + ('bob@windmill.dev', 'dummy_hash', 'password', false, true, 'Bob'), + ('charlie@windmill.dev', 'dummy_hash', 'password', false, true, 'Charlie'), + ('operator@windmill.dev', 'dummy_hash', 'password', false, true, 'Operator') +ON CONFLICT (email) DO NOTHING; + +-- ============================================ +-- TOKENS for authentication +-- ============================================ + +-- Tokens associated with emails (workspace-scoped) +-- The auth system will look up the user by email in the usr table +-- Note: tokens must be at least 10 characters (TOKEN_PREFIX_LEN) +INSERT INTO token (token, email, label, super_admin, owner, workspace_id) +VALUES + ('ADMIN_TOKEN_TEST', 'admin@windmill.dev', 'Admin token', false, 'u/admin', 'test-workspace'), + ('ALICE_TOKEN_TEST', 'alice@windmill.dev', 'Alice token', false, 'u/alice', 'test-workspace'), + ('BOB_TOKEN_TEST12', 'bob@windmill.dev', 'Bob token', false, 'u/bob', 'test-workspace'), + ('CHARLIE_TOKEN_01', 'charlie@windmill.dev', 'Charlie token', false, 'u/charlie', 'test-workspace'), + ('OPERATOR_TOKEN_1', 'operator@windmill.dev', 'Operator token', false, 'u/operator', 'test-workspace'); + +-- ============================================ +-- GROUPS +-- ============================================ + +-- 'developers' group - Charlie is a member +INSERT INTO group_ (workspace_id, name, summary, extra_perms) +VALUES ('test-workspace', 'developers', 'Developer group', '{}'); + +-- 'editors' group - Bob is a member (has write access to some folders) +INSERT INTO group_ (workspace_id, name, summary, extra_perms) +VALUES ('test-workspace', 'editors', 'Editor group', '{}'); + +-- Group memberships +INSERT INTO usr_to_group (workspace_id, group_, usr) +VALUES + ('test-workspace', 'developers', 'charlie'), + ('test-workspace', 'editors', 'bob'); + +-- ============================================ +-- FOLDERS +-- ============================================ + +-- 'shared' folder: +-- - Alice has read-only access (extra_perms: u/alice -> false) +-- - Bob has write access (extra_perms: u/bob -> true) +INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by) +VALUES ('test-workspace', 'shared', 'Shared Folder', '{"u/admin"}', + '{"u/alice": false, "u/bob": true}', 'admin'); + +-- 'team' folder: +-- - 'developers' group has read-only access +INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by) +VALUES ('test-workspace', 'team', 'Team Folder', '{"u/admin"}', + '{"g/developers": false}', 'admin'); + +-- 'editable' folder: +-- - 'editors' group has write access +INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by) +VALUES ('test-workspace', 'editable', 'Editable Folder', '{"u/admin"}', + '{"g/editors": true}', 'admin'); + +-- 'admin_only' folder: +-- - Only admin has access (but we'll add item-level perms for specific items) +INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by) +VALUES ('test-workspace', 'admin_only', 'Admin Only Folder', '{"u/admin"}', + '{}', 'admin'); + +-- 'alice_owned' folder: +-- - Alice is an owner (owners must also be in extra_perms for get_folders_for_user to find them) +INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by) +VALUES ('test-workspace', 'alice_owned', 'Alice Owned Folder', '{"u/alice"}', + '{"u/alice": true}', 'alice'); + +-- ============================================ +-- SCRIPTS +-- ============================================ + +-- Alice's personal script +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1001, 'u/alice/my_script', + 'export function main() { return "alice script"; }', + 'deno', 'script', 'alice', '{}', 'Alice script', '', '', '{}'); + +-- Bob's personal script +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1002, 'u/bob/my_script', + 'export function main() { return "bob script"; }', + 'deno', 'script', 'bob', '{}', 'Bob script', '', '', '{}'); + +-- Script in shared folder (accessible by Alice read-only, Bob write) +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1003, 'f/shared/public_script', + 'export function main() { return "public"; }', + 'deno', 'script', 'admin', '{}', 'Public script', '', '', '{}'); + +-- Script in team folder (accessible by developers group) +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1004, 'f/team/team_script', + 'export function main() { return "team"; }', + 'deno', 'script', 'admin', '{}', 'Team script', '', '', '{}'); + +-- Script in admin_only folder but with explicit share to Alice +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1005, 'f/admin_only/shared_with_alice', + 'export function main() { return "shared"; }', + 'deno', 'script', 'admin', '{}', 'Shared with Alice', '', '', + '{"u/alice": false}'); + +-- Script in alice_owned folder +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1006, 'f/alice_owned/owner_script', + 'export function main() { return "owner"; }', + 'deno', 'script', 'alice', '{}', 'Owner script', '', '', '{}'); + +-- Alice's script with extra_perms sharing to Bob (read-only) +INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms) +VALUES ('test-workspace', 1007, 'u/alice/extra_shared_script', + 'export function main() { return "extra shared"; }', + 'deno', 'script', 'alice', '{}', 'Extra shared script', '', '', + '{"u/bob": false}'); + +-- ============================================ +-- FLOWS (with flow_versions) +-- ============================================ + +-- Create flows first (without versions) +INSERT INTO flow (workspace_id, path, summary, description, value, edited_by, edited_at, schema, extra_perms) +VALUES + ('test-workspace', 'u/alice/my_flow', 'Alice flow', '', '{"modules": []}', 'alice', NOW(), '{}', '{}'), + ('test-workspace', 'u/bob/my_flow', 'Bob flow', '', '{"modules": []}', 'bob', NOW(), '{}', '{}'), + ('test-workspace', 'f/shared/shared_flow', 'Shared flow', '', '{"modules": []}', 'admin', NOW(), '{}', '{}'); + +-- Create flow versions +INSERT INTO flow_version (id, workspace_id, path, value, schema, created_by, created_at) +VALUES + (1001, 'test-workspace', 'u/alice/my_flow', '{"modules": []}', '{}', 'alice', NOW()), + (1002, 'test-workspace', 'u/bob/my_flow', '{"modules": []}', '{}', 'bob', NOW()), + (1003, 'test-workspace', 'f/shared/shared_flow', '{"modules": []}', '{}', 'admin', NOW()); + +-- Update flows with version references +UPDATE flow SET versions = ARRAY[1001::bigint] WHERE path = 'u/alice/my_flow' AND workspace_id = 'test-workspace'; +UPDATE flow SET versions = ARRAY[1002::bigint] WHERE path = 'u/bob/my_flow' AND workspace_id = 'test-workspace'; +UPDATE flow SET versions = ARRAY[1003::bigint] WHERE path = 'f/shared/shared_flow' AND workspace_id = 'test-workspace'; + +-- ============================================ +-- RESOURCES +-- ============================================ + +-- Alice's personal resource +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/alice/my_resource', '{"key": "alice_value"}', + 'Alice resource', 'object', '{}', 'alice'); + +-- Bob's personal resource +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/bob/my_resource', '{"key": "bob_value"}', + 'Bob resource', 'object', '{}', 'bob'); + +-- ============================================ +-- VARIABLES +-- ============================================ + +-- Alice's personal variable (non-secret for testing permissions, not encryption) +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/alice/my_variable', 'alice_value', false, + 'Alice variable', '{}'); + +-- Bob's personal variable (non-secret for testing permissions, not encryption) +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/bob/my_variable', 'bob_value', false, + 'Bob variable', '{}'); + +-- ============================================ +-- SCHEDULES +-- ============================================ + +-- Alice's personal schedule +INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms) +VALUES ('test-workspace', 'u/alice/my_schedule', 'alice', NOW(), '0 * * * *', false, + 'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}'); + +-- Bob's personal schedule +INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms) +VALUES ('test-workspace', 'u/bob/my_schedule', 'bob', NOW(), '0 * * * *', false, + 'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}'); + +-- ============================================ +-- APPS (with app_versions) +-- ============================================ + +-- Alice's personal app +INSERT INTO app (id, workspace_id, path, summary, versions, policy, extra_perms) +VALUES (2001, 'test-workspace', 'u/alice/my_app', 'Alice app', '{}', + '{"on_behalf_of": "u/alice", "on_behalf_of_email": "alice@windmill.dev", "execution_mode": "viewer"}', '{}'); + +-- Shared folder app +INSERT INTO app (id, workspace_id, path, summary, versions, policy, extra_perms) +VALUES (2002, 'test-workspace', 'f/shared/shared_app', 'Shared app', '{}', + '{"on_behalf_of": "u/admin", "on_behalf_of_email": "admin@windmill.dev", "execution_mode": "viewer"}', '{}'); + +-- Create app versions +INSERT INTO app_version (id, app_id, value, created_by, created_at) +VALUES + (2001, 2001, '{"grid": []}', 'alice', NOW()), + (2002, 2002, '{"grid": []}', 'admin', NOW()); + +-- Update apps with version references +UPDATE app SET versions = ARRAY[2001::bigint] WHERE id = 2001; +UPDATE app SET versions = ARRAY[2002::bigint] WHERE id = 2002; diff --git a/backend/windmill-api-integration-tests/tests/fixtures/resources_test.sql b/backend/windmill-api-integration-tests/tests/fixtures/resources_test.sql new file mode 100644 index 0000000000..1d267256cd --- /dev/null +++ b/backend/windmill-api-integration-tests/tests/fixtures/resources_test.sql @@ -0,0 +1,69 @@ +-- Combined fixture for resource endpoint tests + +-- === resource type test data === + +INSERT INTO resource_type (workspace_id, name, schema, description, created_by) +VALUES ('test-workspace', 'test_db', '{"type": "object", "properties": {"host": {"type": "string"}}}', + 'Test DB type', 'test-user'); + +-- === get_value_interpolated test data === + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/simple_resource', '{"host": "localhost", "port": 5432}', + 'Simple resource', 'object', '{}', 'test-user'); + +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/test-user/db_password', 'hunter2', false, 'DB password', '{}'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/resource_with_var', '{"host": "localhost", "password": "$var:u/test-user/db_password"}', + 'Resource with var ref', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/db_credentials', '{"user": "admin", "password": "secret123"}', + 'DB credentials', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/resource_with_res', '{"host": "localhost", "credentials": "$res:u/test-user/db_credentials"}', + 'Resource with res ref', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/resource_mixed', '{"host": "localhost", "password": "$var:u/test-user/db_password", "credentials": "$res:u/test-user/db_credentials"}', + 'Resource with mixed refs', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/null_resource', null, + 'Null resource', 'object', '{}', 'test-user'); + +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/test-user/api_key', 'sk-abc123', false, 'API key', '{}'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/inner_resource', '{"key": "$var:u/test-user/api_key"}', + 'Inner resource', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/chained_resource', '{"service": "myapi", "auth": "$res:u/test-user/inner_resource"}', + 'Chained resource', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/resource_with_array', '{"hosts": ["host1", "host2"], "port": 5432}', + 'Resource with array', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/scalar_var_resource', '"$var:u/test-user/db_password"', + 'Scalar var ref', 'string', '{}', 'test-user'); + +-- === mcp_tools test data === + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/mcp_valid', '{"name": "test-mcp", "url": "http://127.0.0.1:19999/mcp"}', + 'Valid MCP resource (unreachable)', 'mcp_server', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/mcp_invalid_format', '{"host": "localhost", "port": 5432}', + 'Not an MCP resource', 'object', '{}', 'test-user'); + +INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by) +VALUES ('test-workspace', 'u/test-user/mcp_null', null, + 'Null MCP resource', 'mcp_server', '{}', 'test-user'); diff --git a/backend/windmill-api-integration-tests/tests/fixtures/variables_test.sql b/backend/windmill-api-integration-tests/tests/fixtures/variables_test.sql new file mode 100644 index 0000000000..0d3e99e313 --- /dev/null +++ b/backend/windmill-api-integration-tests/tests/fixtures/variables_test.sql @@ -0,0 +1,10 @@ +-- Fixture for variable endpoint tests + +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/test-user/plain_var', 'hello world', false, 'A plain variable', '{}'); + +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/test-user/secret_var', 'supersecret', true, 'A secret variable', '{}'); + +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES ('test-workspace', 'u/test-user/another_var', 'foobar', false, 'Another variable', '{}'); diff --git a/backend/tests/flows.rs b/backend/windmill-api-integration-tests/tests/flows.rs similarity index 99% rename from backend/tests/flows.rs rename to backend/windmill-api-integration-tests/tests/flows.rs index 848231f2fc..ff3f86bf2d 100644 --- a/backend/tests/flows.rs +++ b/backend/windmill-api-integration-tests/tests/flows.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn flow_url(port: u16, endpoint: &str, path: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/flows/{endpoint}/{path}") @@ -40,7 +39,7 @@ fn new_flow(path: &str, summary: &str) -> serde_json::Value { }) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_flow_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/folders.rs b/backend/windmill-api-integration-tests/tests/folders.rs similarity index 98% rename from backend/tests/folders.rs rename to backend/windmill-api-integration-tests/tests/folders.rs index 075eea69eb..3d2ff5b4dd 100644 --- a/backend/tests/folders.rs +++ b/backend/windmill-api-integration-tests/tests/folders.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn folder_url(port: u16, endpoint: &str, name: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/folders/{endpoint}/{name}") @@ -16,7 +15,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder { builder.header("Authorization", "Bearer SECRET_TOKEN") } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_folder_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/groups.rs b/backend/windmill-api-integration-tests/tests/groups.rs similarity index 99% rename from backend/tests/groups.rs rename to backend/windmill-api-integration-tests/tests/groups.rs index 453a15916e..0fd8ee7e08 100644 --- a/backend/tests/groups.rs +++ b/backend/windmill-api-integration-tests/tests/groups.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn group_url(port: u16, endpoint: &str, name: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/groups/{endpoint}/{name}") @@ -16,7 +15,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder { builder.header("Authorization", "Bearer SECRET_TOKEN") } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_group_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/permissions.rs b/backend/windmill-api-integration-tests/tests/permissions.rs similarity index 98% rename from backend/tests/permissions.rs rename to backend/windmill-api-integration-tests/tests/permissions.rs index 0b1604fbb1..329ed5e11d 100644 --- a/backend/tests/permissions.rs +++ b/backend/windmill-api-integration-tests/tests/permissions.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; /// Helper to create a client authenticated as a specific user async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client { @@ -44,7 +43,7 @@ async fn can_write(client: &reqwest::Client, url: &str, body: serde_json::Value) /// `cargo test --features deno_core permissions -- --ignored` #[ignore] #[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base", "permissions_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))] async fn test_permissions_exhaustive(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; @@ -477,7 +476,7 @@ async fn test_permissions_exhaustive(db: Pool) -> anyhow::Result<()> { /// Additional test for verifying group permission inheritance #[ignore] #[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base", "permissions_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))] async fn test_group_permission_inheritance(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; @@ -547,7 +546,7 @@ async fn test_group_permission_inheritance(db: Pool) -> anyhow::Result /// Test that permissions work correctly for all item types #[ignore] #[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base", "permissions_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))] async fn test_all_item_types_permissions(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; @@ -588,7 +587,7 @@ async fn test_all_item_types_permissions(db: Pool) -> anyhow::Result<( /// Operators have limited permissions - they can execute but cannot manage resources #[ignore] #[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base", "permissions_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))] async fn test_operator_cannot_create_update(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/protection_rules.rs b/backend/windmill-api-integration-tests/tests/protection_rules.rs similarity index 98% rename from backend/tests/protection_rules.rs rename to backend/windmill-api-integration-tests/tests/protection_rules.rs index 31198f9c9c..9469554999 100644 --- a/backend/tests/protection_rules.rs +++ b/backend/windmill-api-integration-tests/tests/protection_rules.rs @@ -7,8 +7,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; use windmill_common::workspaces::invalidate_protection_rules_cache; -mod common; -use common::*; +use windmill_test_utils::*; fn client() -> reqwest::Client { reqwest::Client::new() @@ -51,7 +50,7 @@ fn new_flow(path: &str, summary: &str) -> serde_json::Value { /// Comprehensive test for protection rules functionality. /// Tests all essential cases in a single test to avoid cache interference. -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_protection_rules(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; invalidate_protection_rules_cache("test-workspace"); diff --git a/backend/tests/resources.rs b/backend/windmill-api-integration-tests/tests/resources.rs similarity index 98% rename from backend/tests/resources.rs rename to backend/windmill-api-integration-tests/tests/resources.rs index a81daf029e..6294c67308 100644 --- a/backend/tests/resources.rs +++ b/backend/windmill-api-integration-tests/tests/resources.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn resource_url(port: u16, endpoint: &str, path: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/resources/{endpoint}/{path}") @@ -23,7 +22,7 @@ async fn authed_get(port: u16, endpoint: &str, path: &str) -> reqwest::Response .unwrap() } -#[sqlx::test(fixtures("base", "resources_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "resources_test"))] async fn test_resource_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; @@ -400,7 +399,7 @@ async fn test_resource_endpoints(db: Pool) -> anyhow::Result<()> { } #[cfg(feature = "mcp")] -#[sqlx::test(fixtures("base", "resources_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "resources_test"))] async fn test_mcp_tools(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/schedules.rs b/backend/windmill-api-integration-tests/tests/schedules.rs similarity index 98% rename from backend/tests/schedules.rs rename to backend/windmill-api-integration-tests/tests/schedules.rs index ee66264c5f..89ba0ec2cc 100644 --- a/backend/tests/schedules.rs +++ b/backend/windmill-api-integration-tests/tests/schedules.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn schedule_url(port: u16, endpoint: &str, path: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/schedules/{endpoint}/{path}") @@ -23,7 +22,7 @@ async fn authed_get(port: u16, endpoint: &str, path: &str) -> reqwest::Response .unwrap() } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_schedule_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/scripts.rs b/backend/windmill-api-integration-tests/tests/scripts.rs similarity index 99% rename from backend/tests/scripts.rs rename to backend/windmill-api-integration-tests/tests/scripts.rs index 3f55f36a64..74fd9c8611 100644 --- a/backend/tests/scripts.rs +++ b/backend/windmill-api-integration-tests/tests/scripts.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn script_url(port: u16, endpoint: &str, path: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/scripts/{endpoint}/{path}") @@ -39,7 +38,7 @@ fn new_script(path: &str, summary: &str, content: &str) -> serde_json::Value { }) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_script_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/trigger_e2e.rs b/backend/windmill-api-integration-tests/tests/trigger_e2e.rs similarity index 98% rename from backend/tests/trigger_e2e.rs rename to backend/windmill-api-integration-tests/tests/trigger_e2e.rs index 7bfb735a5c..3b0e32749a 100644 --- a/backend/tests/trigger_e2e.rs +++ b/backend/windmill-api-integration-tests/tests/trigger_e2e.rs @@ -24,8 +24,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; use std::time::Duration; -mod common; -use common::*; +use windmill_test_utils::*; // ============================================================================ // Helpers @@ -137,7 +136,7 @@ async fn insert_resource( /// -- --ignored --nocapture /// ``` #[ignore = "requires running MQTT broker on localhost:1883"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_mqtt_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -230,7 +229,7 @@ async fn test_mqtt_e2e(db: Pool) -> anyhow::Result<()> { /// -- --ignored --nocapture /// ``` #[ignore = "requires running WebSocket echo server on localhost:8765"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_websocket_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -286,7 +285,7 @@ async fn test_websocket_e2e(db: Pool) -> anyhow::Result<()> { /// -- --ignored --nocapture /// ``` #[ignore = "requires PostgreSQL with wal_level=logical"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_postgres_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -387,7 +386,7 @@ async fn test_postgres_e2e(db: Pool) -> anyhow::Result<()> { /// ``` #[cfg(all(feature = "enterprise", feature = "private"))] #[ignore = "requires running Kafka broker on localhost:9092"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_kafka_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -478,7 +477,7 @@ async fn test_kafka_e2e(db: Pool) -> anyhow::Result<()> { /// ``` #[cfg(all(feature = "enterprise", feature = "private"))] #[ignore = "requires running NATS server on localhost:4222"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_nats_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -553,7 +552,7 @@ async fn test_nats_e2e(db: Pool) -> anyhow::Result<()> { /// ``` #[cfg(all(feature = "enterprise", feature = "private"))] #[ignore = "requires LocalStack SQS on localhost:4566"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_sqs_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -641,7 +640,7 @@ async fn test_sqs_e2e(db: Pool) -> anyhow::Result<()> { /// ``` #[cfg(all(feature = "enterprise", feature = "private"))] #[ignore = "requires GCP Pub/Sub emulator on localhost:8085"] -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_gcp_e2e(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; diff --git a/backend/tests/triggers.rs b/backend/windmill-api-integration-tests/tests/triggers.rs similarity index 95% rename from backend/tests/triggers.rs rename to backend/windmill-api-integration-tests/tests/triggers.rs index ff7d34decd..46854d566e 100644 --- a/backend/tests/triggers.rs +++ b/backend/windmill-api-integration-tests/tests/triggers.rs @@ -12,14 +12,13 @@ use serde::Deserialize; use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; // ============================================================================ // Capture Config Tests (direct DB) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_config_insert_and_query(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -56,7 +55,7 @@ async fn test_capture_config_insert_and_query(db: Pool) -> anyhow::Res Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_config_upsert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -104,7 +103,7 @@ async fn test_capture_config_upsert(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_config_ping_updates_timestamp(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -159,7 +158,7 @@ async fn test_capture_config_ping_updates_timestamp(db: Pool) -> anyho // Capture Payload Tests (direct DB) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_insert_and_list(db: Pool) -> anyhow::Result<()> { for i in 0..2 { sqlx::query!( @@ -192,7 +191,7 @@ async fn test_capture_insert_and_list(db: Pool) -> anyhow::Result<()> Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_delete(db: Pool) -> anyhow::Result<()> { let id = sqlx::query_scalar!( r#" @@ -227,7 +226,7 @@ async fn test_capture_delete(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_filter_by_trigger_kind(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -304,7 +303,7 @@ struct CaptureResponse { main_args: serde_json::Value, } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_api_set_config_and_list(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -348,7 +347,7 @@ async fn test_capture_api_set_config_and_list(db: Pool) -> anyhow::Res Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_api_list_captures(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -394,7 +393,7 @@ async fn test_capture_api_list_captures(db: Pool) -> anyhow::Result<() Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_api_get_single(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -441,7 +440,7 @@ async fn test_capture_api_get_single(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_api_delete(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -493,7 +492,7 @@ async fn test_capture_api_delete(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_capture_api_pagination(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; @@ -555,7 +554,7 @@ async fn test_capture_api_pagination(db: Pool) -> anyhow::Result<()> { // HTTP Trigger Tests (direct DB) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_http_trigger_insert_and_query(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -610,7 +609,7 @@ async fn test_http_trigger_insert_and_query(db: Pool) -> anyhow::Resul Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_http_trigger_multiple_methods(db: Pool) -> anyhow::Result<()> { let methods = ["get", "post", "put", "delete", "patch"]; @@ -657,7 +656,7 @@ async fn test_http_trigger_multiple_methods(db: Pool) -> anyhow::Resul Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_http_trigger_authentication_methods(db: Pool) -> anyhow::Result<()> { let auth_methods = ["none", "windmill", "api_key", "basic_http", "signature"]; @@ -708,7 +707,7 @@ async fn test_http_trigger_authentication_methods(db: Pool) -> anyhow: Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_http_trigger_update(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -761,7 +760,7 @@ async fn test_http_trigger_update(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_http_trigger_delete(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -813,7 +812,7 @@ async fn test_http_trigger_delete(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_http_trigger_mode_filtering(db: Pool) -> anyhow::Result<()> { let modes = ["enabled", "disabled", "suspended"]; @@ -870,7 +869,7 @@ async fn test_http_trigger_mode_filtering(db: Pool) -> anyhow::Result< // Other Trigger Types Tests (DB schema validation) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_websocket_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -909,7 +908,7 @@ async fn test_websocket_trigger_insert(db: Pool) -> anyhow::Result<()> Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_kafka_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -952,7 +951,7 @@ async fn test_kafka_trigger_insert(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_postgres_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -995,7 +994,7 @@ async fn test_postgres_trigger_insert(db: Pool) -> anyhow::Result<()> Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_nats_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -1038,7 +1037,7 @@ async fn test_nats_trigger_insert(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_sqs_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -1089,7 +1088,7 @@ async fn test_sqs_trigger_insert(db: Pool) -> anyhow::Result<()> { // Cross-trigger tests // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_server_state_tracking(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -1144,7 +1143,7 @@ async fn test_trigger_server_state_tracking(db: Pool) -> anyhow::Resul Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_mode_filtering(db: Pool) -> anyhow::Result<()> { let modes = ["enabled", "disabled", "enabled"]; @@ -1191,7 +1190,7 @@ async fn test_trigger_mode_filtering(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_multiple_capture_configs_per_path(db: Pool) -> anyhow::Result<()> { let trigger_kinds = ["webhook", "email", "kafka"]; @@ -1229,7 +1228,7 @@ async fn test_multiple_capture_configs_per_path(db: Pool) -> anyhow::R // Schedule Tests (DB-level) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_schedule_insert_and_query(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -1277,7 +1276,7 @@ async fn test_schedule_insert_and_query(db: Pool) -> anyhow::Result<() // MQTT Trigger Tests (DB-level) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_mqtt_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query( r#" @@ -1321,7 +1320,7 @@ async fn test_mqtt_trigger_insert(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_mqtt_trigger_update(db: Pool) -> anyhow::Result<()> { sqlx::query( r#" @@ -1366,7 +1365,7 @@ async fn test_mqtt_trigger_update(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_mqtt_trigger_delete(db: Pool) -> anyhow::Result<()> { sqlx::query( r#" @@ -1414,7 +1413,7 @@ async fn test_mqtt_trigger_delete(db: Pool) -> anyhow::Result<()> { // GCP Trigger Tests (DB-level) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_gcp_trigger_insert_pull(db: Pool) -> anyhow::Result<()> { sqlx::query( r#" @@ -1465,7 +1464,7 @@ async fn test_gcp_trigger_insert_pull(db: Pool) -> anyhow::Result<()> Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_gcp_trigger_insert_push(db: Pool) -> anyhow::Result<()> { sqlx::query( r#" @@ -1515,7 +1514,7 @@ async fn test_gcp_trigger_insert_push(db: Pool) -> anyhow::Result<()> Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_gcp_trigger_unique_constraint(db: Pool) -> anyhow::Result<()> { let insert_query = r#" INSERT INTO gcp_trigger ( @@ -1569,7 +1568,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool) -> anyhow::Resul // Email Trigger Tests (DB-level) // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_email_trigger_insert(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -1612,7 +1611,7 @@ async fn test_email_trigger_insert(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_email_trigger_update(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" @@ -1658,7 +1657,7 @@ async fn test_email_trigger_update(db: Pool) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_email_trigger_delete(db: Pool) -> anyhow::Result<()> { sqlx::query!( r#" diff --git a/backend/tests/users.rs b/backend/windmill-api-integration-tests/tests/users.rs similarity index 99% rename from backend/tests/users.rs rename to backend/windmill-api-integration-tests/tests/users.rs index 352b3cc5c6..61e174a002 100644 --- a/backend/tests/users.rs +++ b/backend/windmill-api-integration-tests/tests/users.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn user_url(port: u16, endpoint: &str, name: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/users/{endpoint}/{name}") @@ -16,7 +15,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder { builder.header("Authorization", "Bearer SECRET_TOKEN") } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_user_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/variables.rs b/backend/windmill-api-integration-tests/tests/variables.rs similarity index 98% rename from backend/tests/variables.rs rename to backend/windmill-api-integration-tests/tests/variables.rs index 22892e522a..0d4edaff91 100644 --- a/backend/tests/variables.rs +++ b/backend/windmill-api-integration-tests/tests/variables.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn variable_url(port: u16, endpoint: &str, path: &str) -> String { format!("http://localhost:{port}/api/w/test-workspace/variables/{endpoint}/{path}") @@ -23,7 +22,7 @@ async fn authed_get(port: u16, endpoint: &str, path: &str) -> reqwest::Response .unwrap() } -#[sqlx::test(fixtures("base", "variables_test"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "variables_test"))] async fn test_variable_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/tests/workspace_comparison.rs b/backend/windmill-api-integration-tests/tests/workspace_comparison.rs similarity index 99% rename from backend/tests/workspace_comparison.rs rename to backend/windmill-api-integration-tests/tests/workspace_comparison.rs index 5761d6e051..f9cb6037c5 100644 --- a/backend/tests/workspace_comparison.rs +++ b/backend/windmill-api-integration-tests/tests/workspace_comparison.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; /// Comprehensive integration test for the compare_workspaces endpoint. /// @@ -12,7 +11,7 @@ use common::*; /// 3. Making various changes in both workspaces (new items, modifications, conflicts, deletions, renames) /// 4. Populating the workspace_diff table to simulate Git sync tracking /// 5. Calling compare_workspaces and verifying all aspects of the comparison -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_compare_workspaces_comprehensive(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; diff --git a/backend/tests/workspaces.rs b/backend/windmill-api-integration-tests/tests/workspaces.rs similarity index 99% rename from backend/tests/workspaces.rs rename to backend/windmill-api-integration-tests/tests/workspaces.rs index 4a7507b155..58c07965dc 100644 --- a/backend/tests/workspaces.rs +++ b/backend/windmill-api-integration-tests/tests/workspaces.rs @@ -1,8 +1,7 @@ use serde_json::json; use sqlx::{Pool, Postgres}; -mod common; -use common::*; +use windmill_test_utils::*; fn client() -> reqwest::Client { reqwest::Client::new() @@ -12,7 +11,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder { builder.header("Authorization", "Bearer SECRET_TOKEN") } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_workspace_endpoints(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; diff --git a/backend/windmill-common/tests/fixtures/base.sql b/backend/windmill-common/tests/fixtures/base.sql new file mode 100644 index 0000000000..7db9918fba --- /dev/null +++ b/backend/windmill-common/tests/fixtures/base.sql @@ -0,0 +1,146 @@ +-- used for backend automated testing +-- https://docs.rs/sqlx/latest/sqlx/attr.test.html + +INSERT INTO workspace + (id, name, owner) + VALUES ('test-workspace', 'test-workspace', 'test-user'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin'); + +INSERT INTO workspace_key(workspace_id, kind, key) VALUES + ('test-workspace', 'cloud', 'test-key'); + + +INSERT INTO workspace_settings (workspace_id) VALUES + ('test-workspace'); + +INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES + ('test-workspace', 'all', 'All users', '{}'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username) + VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) + VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) + VALUES ('test3@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 3'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test3@windmill.dev', 'test-user-3', false, 'User'); + +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true); +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false); +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_3', 'test3@windmill.dev', 'test token 3', false); + +GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin; +GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user; + +CREATE FUNCTION "notify_insert_on_completed_job" () +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('completed', NEW.id::text); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + + CREATE TRIGGER "notify_insert_on_completed_job" + AFTER INSERT ON "v2_job_completed" + FOR EACH ROW +EXECUTE FUNCTION "notify_insert_on_completed_job" (); + + +CREATE FUNCTION "notify_queue" () +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('queued', NEW.id::text); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + + CREATE TRIGGER "notify_queue_after_insert" + AFTER INSERT ON "v2_job_queue" + FOR EACH ROW +EXECUTE FUNCTION "notify_queue" (); + + CREATE TRIGGER "notify_queue_after_flow_status_update" + AFTER UPDATE ON "v2_job_status" + FOR EACH ROW + WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status) +EXECUTE FUNCTION "notify_queue" (); + +-- Apply phase 4: +DROP FUNCTION IF EXISTS v2_job_after_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; + +DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE; + +ALTER TABLE v2_job_queue + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __last_ping CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __flow_status CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __same_worker CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __pre_run_error CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __mem_peak CASCADE, + DROP COLUMN IF EXISTS __root_job CASCADE, + DROP COLUMN IF EXISTS __leaf_jobs CASCADE, + DROP COLUMN IF EXISTS __concurrent_limit CASCADE, + DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE, + DROP COLUMN IF EXISTS __timeout CASCADE, + DROP COLUMN IF EXISTS __flow_step_id CASCADE, + DROP COLUMN IF EXISTS __cache_ttl CASCADE; + +LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; +ALTER TABLE v2_job_completed + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __created_at CASCADE, + DROP COLUMN IF EXISTS __success CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __is_skipped CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __tag CASCADE, + DROP COLUMN IF EXISTS __priority CASCADE; diff --git a/backend/windmill-common/tests/fixtures/instance_group_auto_add.sql b/backend/windmill-common/tests/fixtures/instance_group_auto_add.sql new file mode 100644 index 0000000000..599abd7ea2 --- /dev/null +++ b/backend/windmill-common/tests/fixtures/instance_group_auto_add.sql @@ -0,0 +1,76 @@ +-- Fixture for instance group auto-add tests +-- Sets up test data for testing the interaction between instance groups and workspace auto-add + +-- Create additional workspaces for testing +INSERT INTO workspace (id, name, owner) +VALUES + ('ws-with-auto-add', 'Workspace with Auto Add', 'admin'), + ('ws-no-auto-add', 'Workspace without Auto Add', 'admin'), + ('ws-multi-group', 'Workspace with Multiple Groups', 'admin') +ON CONFLICT DO NOTHING; + +INSERT INTO workspace_settings (workspace_id) +VALUES + ('ws-with-auto-add'), + ('ws-no-auto-add'), + ('ws-multi-group') +ON CONFLICT DO NOTHING; + +INSERT INTO workspace_key(workspace_id, kind, key) +VALUES + ('ws-with-auto-add', 'cloud', 'key-auto-add'), + ('ws-no-auto-add', 'cloud', 'key-no-auto'), + ('ws-multi-group', 'cloud', 'key-multi') +ON CONFLICT DO NOTHING; + +-- Create instance groups +INSERT INTO instance_group (name, summary, id) +VALUES + ('engineering', 'Engineering team', 'eng-uuid-001'), + ('sales', 'Sales team', 'sales-uuid-002'), + ('admins', 'Admin group', 'admin-uuid-003') +ON CONFLICT DO NOTHING; + +-- Create users in the password table (required for auto-add eligibility) +INSERT INTO password (email, password_hash, login_type, super_admin, verified, name) +VALUES + ('alice@example.com', 'hash1', 'password', false, true, 'Alice'), + ('bob@example.com', 'hash2', 'password', false, true, 'Bob'), + ('charlie@example.com', 'hash3', 'password', false, true, 'Charlie'), + ('dave@example.com', 'hash4', 'password', false, true, 'Dave'), + ('admin@windmill.dev', 'hash5', 'password', true, true, 'Admin') +ON CONFLICT DO NOTHING; + +-- Add users to instance groups +INSERT INTO email_to_igroup (email, igroup) +VALUES + ('alice@example.com', 'engineering'), + ('bob@example.com', 'engineering'), + ('bob@example.com', 'sales'), + ('charlie@example.com', 'sales'), + ('dave@example.com', 'admins') +ON CONFLICT DO NOTHING; + +-- Create admin user in workspaces for running tests +INSERT INTO usr (workspace_id, username, email, is_admin, operator) +VALUES + ('ws-with-auto-add', 'admin', 'admin@windmill.dev', true, false), + ('ws-no-auto-add', 'admin', 'admin@windmill.dev', true, false), + ('ws-multi-group', 'admin', 'admin@windmill.dev', true, false) +ON CONFLICT DO NOTHING; + +-- Create the 'all' group for each workspace (required for user auto-add) +INSERT INTO group_ (workspace_id, name, summary, extra_perms) +VALUES + ('ws-with-auto-add', 'all', 'All users', '{}'), + ('ws-no-auto-add', 'all', 'All users', '{}'), + ('ws-multi-group', 'all', 'All users', '{}') +ON CONFLICT DO NOTHING; + +-- Add admin to the 'all' group +INSERT INTO usr_to_group (workspace_id, usr, group_) +VALUES + ('ws-with-auto-add', 'admin', 'all'), + ('ws-no-auto-add', 'admin', 'all'), + ('ws-multi-group', 'admin', 'all') +ON CONFLICT DO NOTHING; diff --git a/backend/windmill-common/tests/fixtures/secret_backend.sql b/backend/windmill-common/tests/fixtures/secret_backend.sql new file mode 100644 index 0000000000..1ebbc13e9b --- /dev/null +++ b/backend/windmill-common/tests/fixtures/secret_backend.sql @@ -0,0 +1,31 @@ +-- Fixture for secret backend migration tests +-- Sets up test secrets in the variable table + +-- Create a second workspace for testing workspace isolation +INSERT INTO workspace (id, name, owner) +VALUES ('test-workspace-2', 'test-workspace-2', 'test-user') +ON CONFLICT DO NOTHING; + +INSERT INTO workspace_settings (workspace_id) +VALUES ('test-workspace-2') +ON CONFLICT DO NOTHING; + +INSERT INTO workspace_key(workspace_id, kind, key) +VALUES ('test-workspace-2', 'cloud', 'test-key-2') +ON CONFLICT DO NOTHING; + +-- Insert test variables with placeholder values. +-- Secret values are encrypted by the test setup using build_crypt + encrypt +-- with the workspace key, matching production behavior. +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES + ('test-workspace', 'u/test-user/db_password', 'PLACEHOLDER', true, 'Database password', '{}'), + ('test-workspace', 'u/test-user/api_key', 'PLACEHOLDER', true, 'API key for external service', '{}'), + ('test-workspace', 'u/test-user/public_var', 'not-a-secret', false, 'A non-secret variable', '{}') +ON CONFLICT DO NOTHING; + +-- Insert test secrets for workspace 2 (to test isolation) +INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms) +VALUES + ('test-workspace-2', 'u/test-user/other_secret', 'PLACEHOLDER', true, 'Secret in workspace 2', '{}') +ON CONFLICT DO NOTHING; diff --git a/backend/tests/instance_group_auto_add.rs b/backend/windmill-common/tests/instance_group_auto_add.rs similarity index 96% rename from backend/tests/instance_group_auto_add.rs rename to backend/windmill-common/tests/instance_group_auto_add.rs index 60ebd3c922..e8a6c841c6 100644 --- a/backend/tests/instance_group_auto_add.rs +++ b/backend/windmill-common/tests/instance_group_auto_add.rs @@ -31,7 +31,7 @@ mod tests { /// Test that configuring instance groups for a workspace auto-adds existing group members #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_configure_instance_groups_adds_existing_members(db: Pool) { // Configure workspace to auto-add users from 'engineering' group with 'developer' role let groups = vec!["engineering".to_string()]; @@ -158,7 +158,7 @@ mod tests { /// Test role assignment based on instance group configuration #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_role_assignment_admin(db: Pool) { // Configure workspace with admins group having admin role let groups = vec!["admins".to_string()]; @@ -209,7 +209,7 @@ mod tests { /// Test role assignment for operator #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_role_assignment_operator(db: Pool) { // Configure workspace with sales group having operator role let groups = vec!["sales".to_string()]; @@ -260,7 +260,7 @@ mod tests { /// Test role precedence when user is in multiple instance groups #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_role_precedence_multiple_groups(db: Pool) { // Configure workspace with multiple groups: engineering (admin), sales (operator) // Bob is in both groups, should get admin role (highest precedence) @@ -322,7 +322,7 @@ mod tests { /// Test removing user from instance group removes them from workspace #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_remove_user_from_instance_group(db: Pool) { // First, add alice to the workspace via engineering group let added_via = json!({"source": "instance_group", "group": "engineering"}); @@ -416,7 +416,7 @@ mod tests { /// Test that users added via domain are not affected by instance group removal #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_domain_added_users_not_affected_by_group_removal(db: Pool) { // Add alice via domain (not instance group) let added_via = json!({"source": "domain", "domain": "example.com"}); @@ -469,7 +469,7 @@ mod tests { /// Test cleanup when instance group is removed from workspace configuration #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_cleanup_removed_instance_groups(db: Pool) { // First, add users via engineering group for (username, email) in &[("alice", "alice@example.com"), ("bob", "bob@example.com")] { @@ -588,7 +588,7 @@ mod tests { /// Test that users are not duplicated if already in workspace #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_no_duplicate_users(db: Pool) { // Add alice to workspace first (without instance group tracking) sqlx::query!( @@ -636,7 +636,7 @@ mod tests { /// Test workspace without auto-add configured is not affected #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_workspace_without_auto_add_not_affected(db: Pool) { // ws-no-auto-add has no instance_groups configured @@ -672,7 +672,7 @@ mod tests { /// Test querying workspaces configured with a specific instance group #[ignore = "requires database setup - run with --ignored flag"] - #[sqlx::test(fixtures("base", "instance_group_auto_add"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))] async fn test_query_workspaces_with_instance_group(db: Pool) { // Configure ws-with-auto-add to use engineering group let groups = vec!["engineering".to_string()]; diff --git a/backend/tests/notify_events.rs b/backend/windmill-common/tests/notify_events.rs similarity index 95% rename from backend/tests/notify_events.rs rename to backend/windmill-common/tests/notify_events.rs index a85a2f1c5a..36302b8e66 100644 --- a/backend/tests/notify_events.rs +++ b/backend/windmill-common/tests/notify_events.rs @@ -11,8 +11,6 @@ use sqlx::{Pool, Postgres}; use windmill_common::notify_events::{cleanup_old_events, get_latest_event_id, poll_notify_events}; -mod common; - /// Helper to insert a test event directly async fn insert_test_event(db: &Pool, channel: &str, payload: &str) -> i64 { sqlx::query_scalar::<_, i64>( @@ -39,7 +37,7 @@ async fn count_events_for_channel(db: &Pool, channel: &str) -> i64 { // Basic Functionality Tests // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_get_latest_event_id_returns_valid_id(db: Pool) { // Get current latest id let latest_id = get_latest_event_id(&db).await.expect("Should get latest event id"); @@ -51,7 +49,7 @@ async fn test_get_latest_event_id_returns_valid_id(db: Pool) { assert!(new_latest_id >= new_id, "Latest id should be >= new event id"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_get_latest_event_id_with_events(db: Pool) { let _id1 = insert_test_event(&db, "test_channel_1", "payload1").await; let _id2 = insert_test_event(&db, "test_channel_2", "payload2").await; @@ -61,7 +59,7 @@ async fn test_get_latest_event_id_with_events(db: Pool) { assert!(latest_id >= id3, "Latest id should be >= last inserted id"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_poll_notify_events_no_new_events(db: Pool) { // Get latest id first let latest_id = get_latest_event_id(&db).await.unwrap(); @@ -71,7 +69,7 @@ async fn test_poll_notify_events_no_new_events(db: Pool) { assert!(events.is_empty(), "Should return empty vec when polling from latest id"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_poll_notify_events_returns_new_events(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -92,7 +90,7 @@ async fn test_poll_notify_events_returns_new_events(db: Pool) { assert!(our_events[0].id < our_events[1].id, "Events should be ordered by id ascending"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_poll_notify_events_respects_last_event_id(db: Pool) { let id1 = insert_test_event(&db, "test_respect_id", "payload1").await; let _id2 = insert_test_event(&db, "test_respect_id", "payload2").await; @@ -109,7 +107,7 @@ async fn test_poll_notify_events_respects_last_event_id(db: Pool) { assert!(our_events.iter().all(|e| e.id > id1), "All events should have id > id1"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_cleanup_old_events(db: Pool) { // Use unique channel names to avoid interference from other tests let old_channel = format!("test_cleanup_old_{}", uuid::Uuid::new_v4()); @@ -156,7 +154,7 @@ async fn test_cleanup_old_events(db: Pool) { // Database Trigger Tests - Verify triggers insert events correctly // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_config_change(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -178,7 +176,7 @@ async fn test_trigger_notify_config_change(db: Pool) { assert!(!config_events.is_empty(), "Should have notify_config_change event"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_global_setting_change_insert(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -201,7 +199,7 @@ async fn test_trigger_notify_global_setting_change_insert(db: Pool) { assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on insert"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_global_setting_change_update(db: Pool) { // Use a unique setting name for testing let setting_name = format!("test_setting_update_{}", uuid::Uuid::new_v4()); @@ -231,7 +229,7 @@ async fn test_trigger_notify_global_setting_change_update(db: Pool) { assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on update"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_global_setting_change_delete(db: Pool) { // Use a unique setting name for testing let setting_name = format!("test_setting_delete_{}", uuid::Uuid::new_v4()); @@ -261,7 +259,7 @@ async fn test_trigger_notify_global_setting_change_delete(db: Pool) { assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on delete"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_workspace_envs_change(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -283,7 +281,7 @@ async fn test_trigger_notify_workspace_envs_change(db: Pool) { assert!(!env_events.is_empty(), "Should have notify_workspace_envs_change event"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_workspace_key_change(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -305,7 +303,7 @@ async fn test_trigger_notify_workspace_key_change(db: Pool) { assert!(!key_events.is_empty(), "Should have notify_workspace_key_change event"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_token_invalidation(db: Pool) { // First insert a session token let token = format!("test_token_{}", uuid::Uuid::new_v4()); @@ -336,7 +334,7 @@ async fn test_trigger_notify_token_invalidation(db: Pool) { assert!(!token_events.is_empty(), "Should have notify_token_invalidation event"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_webhook_change(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -355,7 +353,7 @@ async fn test_trigger_notify_webhook_change(db: Pool) { assert!(!webhook_events.is_empty(), "Should have notify_webhook_change event"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_workspace_premium_change(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -378,7 +376,7 @@ async fn test_trigger_notify_workspace_premium_change(db: Pool) { // HTTP Trigger Tests // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_http_trigger_change(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -408,7 +406,7 @@ async fn test_trigger_notify_http_trigger_change(db: Pool) { // Script/Flow Version Change Tests // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_runnable_version_change_script(db: Pool) { // First create a script without lock let script_path = format!("f/test/script_{}", uuid::Uuid::new_v4()); @@ -449,7 +447,7 @@ async fn test_trigger_notify_runnable_version_change_script(db: Pool) assert_eq!(parts[1], "script", "Second part should be 'script'"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_trigger_notify_runnable_version_change_flow(db: Pool) { // First create a flow with empty versions array let flow_path = format!("f/test/flow_{}", uuid::Uuid::new_v4()); @@ -494,7 +492,7 @@ async fn test_trigger_notify_runnable_version_change_flow(db: Pool) { // Concurrent Access Tests // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_concurrent_event_insertion(db: Pool) { // Use a unique channel name for this test run let channel = format!("test_concurrent_{}", uuid::Uuid::new_v4()); @@ -536,7 +534,7 @@ async fn test_concurrent_event_insertion(db: Pool) { assert_eq!(ids.len(), 10, "All events should have unique IDs"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_polling_isolation(db: Pool) { // Use a unique channel name for this test let channel = format!("test_isolation_{}", uuid::Uuid::new_v4()); @@ -591,7 +589,7 @@ async fn test_polling_isolation(db: Pool) { // Edge Case Tests // ============================================================================ -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_empty_payload(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -607,7 +605,7 @@ async fn test_empty_payload(db: Pool) { assert_eq!(empty_events[0].payload, "", "Payload should be empty string"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_large_payload(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -625,7 +623,7 @@ async fn test_large_payload(db: Pool) { assert_eq!(large_events[0].payload.len(), 1024, "Payload should be preserved"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_special_characters_in_payload(db: Pool) { let before_id = get_latest_event_id(&db).await.unwrap(); @@ -642,7 +640,7 @@ async fn test_special_characters_in_payload(db: Pool) { assert_eq!(special_events[0].payload, special_payload, "Special characters should be preserved"); } -#[sqlx::test(fixtures("base"))] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] async fn test_cleanup_with_no_old_events(db: Pool) { // Use a unique channel name for this test let channel = format!("test_no_old_{}", uuid::Uuid::new_v4()); diff --git a/backend/tests/secret_backend_integration.rs b/backend/windmill-common/tests/secret_backend_integration.rs similarity index 96% rename from backend/tests/secret_backend_integration.rs rename to backend/windmill-common/tests/secret_backend_integration.rs index f9f0fe9f5f..817a8d0e65 100644 --- a/backend/tests/secret_backend_integration.rs +++ b/backend/windmill-common/tests/secret_backend_integration.rs @@ -114,7 +114,7 @@ mod tests { // ==================== Static Token Tests ==================== /// Test Vault connection with static token - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_vault_connection_static_token(db: Pool) { skip_if_no_vault!(); @@ -132,7 +132,7 @@ mod tests { } /// Test basic CRUD operations with static token - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_vault_crud_static_token(_db: Pool) { skip_if_no_vault!(); @@ -193,7 +193,7 @@ mod tests { /// Test Vault connection with JWT authentication #[cfg(feature = "openidconnect")] - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_vault_connection_jwt(db: Pool) { skip_if_no_vault!(); setup_base_url().await; @@ -215,7 +215,7 @@ mod tests { /// Test basic CRUD operations with JWT authentication #[cfg(feature = "openidconnect")] - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_vault_crud_jwt(db: Pool) { skip_if_no_vault!(); setup_base_url().await; @@ -260,7 +260,7 @@ mod tests { // ==================== Migration Tests ==================== /// Test migration from database to Vault - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_migrate_db_to_vault(db: Pool) { skip_if_no_vault!(); @@ -314,7 +314,7 @@ mod tests { } /// Test migration from Vault back to database - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_migrate_vault_to_db(db: Pool) { skip_if_no_vault!(); @@ -373,7 +373,7 @@ mod tests { // ==================== Variable Rename Tests ==================== /// Test renaming a variable path in Vault - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_variable_rename(db: Pool) { skip_if_no_vault!(); let _ = &db; // suppress unused warning @@ -445,7 +445,7 @@ mod tests { // ==================== Full Round Trip Test ==================== /// Test full round-trip: DB -> Vault -> DB with verification - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_full_round_trip(db: Pool) { skip_if_no_vault!(); @@ -501,7 +501,7 @@ mod tests { // ==================== Workspace Isolation Test ==================== /// Test that workspace isolation is maintained - #[sqlx::test(fixtures("base", "secret_backend"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] async fn test_workspace_isolation(db: Pool) { skip_if_no_vault!(); diff --git a/backend/tests/secret_backend_migration.rs b/backend/windmill-common/tests/secret_backend_migration.rs similarity index 96% rename from backend/tests/secret_backend_migration.rs rename to backend/windmill-common/tests/secret_backend_migration.rs index d78da8603c..baa2940319 100644 --- a/backend/tests/secret_backend_migration.rs +++ b/backend/windmill-common/tests/secret_backend_migration.rs @@ -27,8 +27,6 @@ use windmill_common::secret_backend::{ SecretBackend, VaultSettings, }; -mod common; - fn test_vault_settings() -> VaultSettings { VaultSettings { address: std::env::var("VAULT_ADDR").unwrap_or_else(|_| "http://127.0.0.1:8200".to_string()), @@ -42,7 +40,7 @@ fn test_vault_settings() -> VaultSettings { } /// Test that we can connect to Vault -#[sqlx::test(fixtures("base", "secret_backend"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] #[ignore = "requires running Vault instance"] async fn test_vault_connection_works(db: Pool) { let settings = test_vault_settings(); @@ -53,7 +51,7 @@ async fn test_vault_connection_works(db: Pool) { } /// Test migration from database to Vault -#[sqlx::test(fixtures("base", "secret_backend"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] #[ignore = "requires running Vault instance"] async fn test_migrate_db_to_vault(db: Pool) { let settings = test_vault_settings(); @@ -119,7 +117,7 @@ async fn test_migrate_db_to_vault(db: Pool) { } /// Test migration from Vault to database -#[sqlx::test(fixtures("base", "secret_backend"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] #[ignore = "requires running Vault instance"] async fn test_migrate_vault_to_db(db: Pool) { let settings = test_vault_settings(); @@ -196,7 +194,7 @@ async fn test_migrate_vault_to_db(db: Pool) { } /// Test full round-trip migration: DB -> Vault -> DB -#[sqlx::test(fixtures("base", "secret_backend"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] #[ignore = "requires running Vault instance"] async fn test_full_round_trip_migration(db: Pool) { let settings = test_vault_settings(); @@ -276,7 +274,7 @@ async fn test_full_round_trip_migration(db: Pool) { } /// Test that workspace isolation is maintained during migration -#[sqlx::test(fixtures("base", "secret_backend"))] +#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))] #[ignore = "requires running Vault instance"] async fn test_workspace_isolation(db: Pool) { let settings = test_vault_settings(); diff --git a/backend/windmill-queue/tests/fixtures/base.sql b/backend/windmill-queue/tests/fixtures/base.sql new file mode 100644 index 0000000000..7db9918fba --- /dev/null +++ b/backend/windmill-queue/tests/fixtures/base.sql @@ -0,0 +1,146 @@ +-- used for backend automated testing +-- https://docs.rs/sqlx/latest/sqlx/attr.test.html + +INSERT INTO workspace + (id, name, owner) + VALUES ('test-workspace', 'test-workspace', 'test-user'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin'); + +INSERT INTO workspace_key(workspace_id, kind, key) VALUES + ('test-workspace', 'cloud', 'test-key'); + + +INSERT INTO workspace_settings (workspace_id) VALUES + ('test-workspace'); + +INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES + ('test-workspace', 'all', 'All users', '{}'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username) + VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) + VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2'); + +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) + VALUES ('test3@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 3'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES + ('test-workspace', 'test3@windmill.dev', 'test-user-3', false, 'User'); + +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true); +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false); +insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_3', 'test3@windmill.dev', 'test token 3', false); + +GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin; +GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user; + +CREATE FUNCTION "notify_insert_on_completed_job" () +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('completed', NEW.id::text); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + + CREATE TRIGGER "notify_insert_on_completed_job" + AFTER INSERT ON "v2_job_completed" + FOR EACH ROW +EXECUTE FUNCTION "notify_insert_on_completed_job" (); + + +CREATE FUNCTION "notify_queue" () +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('queued', NEW.id::text); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + + CREATE TRIGGER "notify_queue_after_insert" + AFTER INSERT ON "v2_job_queue" + FOR EACH ROW +EXECUTE FUNCTION "notify_queue" (); + + CREATE TRIGGER "notify_queue_after_flow_status_update" + AFTER UPDATE ON "v2_job_status" + FOR EACH ROW + WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status) +EXECUTE FUNCTION "notify_queue" (); + +-- Apply phase 4: +DROP FUNCTION IF EXISTS v2_job_after_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; + +DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE; + +ALTER TABLE v2_job_queue + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __last_ping CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __flow_status CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __same_worker CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __pre_run_error CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __mem_peak CASCADE, + DROP COLUMN IF EXISTS __root_job CASCADE, + DROP COLUMN IF EXISTS __leaf_jobs CASCADE, + DROP COLUMN IF EXISTS __concurrent_limit CASCADE, + DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE, + DROP COLUMN IF EXISTS __timeout CASCADE, + DROP COLUMN IF EXISTS __flow_step_id CASCADE, + DROP COLUMN IF EXISTS __cache_ttl CASCADE; + +LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; +ALTER TABLE v2_job_completed + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __created_at CASCADE, + DROP COLUMN IF EXISTS __success CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __is_skipped CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __tag CASCADE, + DROP COLUMN IF EXISTS __priority CASCADE; diff --git a/backend/windmill-queue/tests/fixtures/schedule_push.sql b/backend/windmill-queue/tests/fixtures/schedule_push.sql new file mode 100644 index 0000000000..9312d0bbd9 --- /dev/null +++ b/backend/windmill-queue/tests/fixtures/schedule_push.sql @@ -0,0 +1,84 @@ +-- Fixture for schedule push tests +-- Sets up scripts, flows, users, and schedules needed to test push_scheduled_job + +-- Password entries for auth resolution +INSERT INTO password (email, password_hash, login_type, super_admin, verified, name) +VALUES + ('test@windmill.dev', 'dummy_hash', 'password', false, true, 'Test User'), + ('obo@windmill.dev', 'dummy_hash', 'password', false, true, 'OBO User') +ON CONFLICT (email) DO NOTHING; + +-- OBO user in workspace +INSERT INTO usr (workspace_id, email, username, is_admin, role) +VALUES ('test-workspace', 'obo@windmill.dev', 'obo-user', false, 'Developer') +ON CONFLICT (workspace_id, username) DO NOTHING; + +-- A simple script +INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind) +VALUES ( + 'test-workspace', 'test-user', + 'export async function main() { return "ok"; }', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + 'Test script', '', 'f/system/test_script', 100001, 'deno', '', 'script' +); + +-- A script with on_behalf_of_email +INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, on_behalf_of_email) +VALUES ( + 'test-workspace', 'test-user', + 'export async function main() { return "obo"; }', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + 'OBO script', '', 'f/system/obo_script', 100002, 'deno', '', 'script', 'obo@windmill.dev' +); + +-- A script with a tag +INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, tag) +VALUES ( + 'test-workspace', 'test-user', + 'export async function main() { return "tagged"; }', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + 'Tagged script', '', 'f/system/tagged_script', 100003, 'deno', '', 'script', 'custom-tag' +); + +-- A script with timeout +INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, timeout) +VALUES ( + 'test-workspace', 'test-user', + 'export async function main() { return "timeout"; }', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + 'Timeout script', '', 'f/system/timeout_script', 100004, 'deno', '', 'script', 300 +); + +-- A flow +INSERT INTO flow (workspace_id, summary, description, path, versions, schema, value, edited_by) +VALUES ( + 'test-workspace', 'Test flow', '', 'f/system/test_flow', '{200001}', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + '{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}', + 'test-user' +); + +INSERT INTO flow_version (id, workspace_id, path, schema, value, created_by) +VALUES ( + 200001, 'test-workspace', 'f/system/test_flow', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + '{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}', + 'test-user' +); + +-- A flow with on_behalf_of_email +INSERT INTO flow (workspace_id, summary, description, path, versions, schema, value, edited_by, on_behalf_of_email) +VALUES ( + 'test-workspace', 'OBO flow', '', 'f/system/obo_flow', '{200002}', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + '{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}', + 'test-user', 'obo@windmill.dev' +); + +INSERT INTO flow_version (id, workspace_id, path, schema, value, created_by) +VALUES ( + 200002, 'test-workspace', 'f/system/obo_flow', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + '{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}', + 'test-user' +); diff --git a/backend/tests/job_cleanup_test.rs b/backend/windmill-queue/tests/job_cleanup_test.rs similarity index 100% rename from backend/tests/job_cleanup_test.rs rename to backend/windmill-queue/tests/job_cleanup_test.rs diff --git a/backend/tests/schedule_push.rs b/backend/windmill-queue/tests/schedule_push.rs similarity index 95% rename from backend/tests/schedule_push.rs rename to backend/windmill-queue/tests/schedule_push.rs index b26336b637..f946234670 100644 --- a/backend/tests/schedule_push.rs +++ b/backend/windmill-queue/tests/schedule_push.rs @@ -1,5 +1,3 @@ -mod common; - mod schedule_push { use chrono::Utc; use sqlx::{Pool, Postgres}; @@ -118,7 +116,7 @@ mod schedule_push { // push_scheduled_job: basic script schedule // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_script_schedule(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let authed = make_authed(); @@ -140,7 +138,7 @@ mod schedule_push { // push_scheduled_job: flow schedule // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_flow_schedule(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.is_flow = true; @@ -164,7 +162,7 @@ mod schedule_push { // push_scheduled_job: on_behalf_of_email (script) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_script_on_behalf_of_email(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.script_path = "f/system/obo_script".to_string(); @@ -191,7 +189,7 @@ mod schedule_push { // push_scheduled_job: on_behalf_of_email (flow) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_flow_on_behalf_of_email(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.is_flow = true; @@ -218,7 +216,7 @@ mod schedule_push { // push_scheduled_job: with retry wraps in SingleStepFlow // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_script_with_retry(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.retry = Some(serde_json::json!({ @@ -247,7 +245,7 @@ mod schedule_push { // push_scheduled_job: duplicate detection (same schedule + time = skip) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_duplicate_skipped(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let authed = make_authed(); @@ -270,7 +268,7 @@ mod schedule_push { // push_scheduled_job: invalid timezone // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_invalid_timezone(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.timezone = "Invalid/Timezone".to_string(); @@ -288,7 +286,7 @@ mod schedule_push { // push_scheduled_job: invalid cron expression // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_invalid_cron(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.schedule = "not a cron".to_string(); @@ -306,7 +304,7 @@ mod schedule_push { // push_scheduled_job: invalid args (not a dict) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_invalid_args(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { let raw = serde_json::value::RawValue::from_string("[1,2,3]".to_string()).unwrap(); @@ -325,7 +323,7 @@ mod schedule_push { // push_scheduled_job: with schedule args passed to job // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_with_args(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { let raw = @@ -353,7 +351,7 @@ mod schedule_push { // push_scheduled_job: script not found // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_script_not_found(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.script_path = "f/system/nonexistent".to_string(); @@ -371,7 +369,7 @@ mod schedule_push { // push_scheduled_job: flow not found // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_flow_not_found(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.is_flow = true; @@ -390,7 +388,7 @@ mod schedule_push { // push_scheduled_job: paused schedule (paused_until in future) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_paused_schedule(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.paused_until = Some(Utc::now() + chrono::Duration::hours(1)); @@ -417,7 +415,7 @@ mod schedule_push { // push_scheduled_job: clock shift detection (now_cutoff >= now) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_clock_shift(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let authed = make_authed(); @@ -445,7 +443,7 @@ mod schedule_push { // try_schedule_next_job: disabled schedule does not push // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_handle_disabled_schedule(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.enabled = false; @@ -471,7 +469,7 @@ mod schedule_push { // try_schedule_next_job: script path mismatch does not push // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_handle_path_mismatch(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let job = make_completed_job(&schedule); @@ -495,7 +493,7 @@ mod schedule_push { // try_schedule_next_job: enabled + matching path pushes next job // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_handle_enabled_pushes_next_job(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let job = make_completed_job(&schedule); @@ -524,7 +522,7 @@ mod schedule_push { // try_schedule_next_job: on_behalf_of_email via handle path // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_handle_on_behalf_of_email(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.script_path = "f/system/obo_script".to_string(); @@ -559,7 +557,7 @@ mod schedule_push { // (caller is responsible for retry + eventual disable) // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_handle_push_failure_disables_schedule(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -604,7 +602,7 @@ mod schedule_push { // If the caller commits, both the next tick and any prior writes persist. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_success_atomic_with_commit(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -646,7 +644,7 @@ mod schedule_push { // Ensures no next tick leaks when the outer tx is not committed. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_success_rolls_back_on_drop(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let job = make_completed_job(&schedule); @@ -675,7 +673,7 @@ mod schedule_push { // The schedule must stay enabled when the caller doesn't commit. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failure_disable_rolls_back_on_drop(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -721,7 +719,7 @@ mod schedule_push { // The caller can perform additional writes on the returned tx. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_tx_usable_after_success(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|_| {}); let job = make_completed_job(&schedule); @@ -759,7 +757,7 @@ mod schedule_push { // The caller can still write on the returned tx after a schedule disable. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_tx_usable_after_failure(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -815,7 +813,7 @@ mod schedule_push { // try_schedule_next_job: flow schedule pushes next job // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_try_schedule_flow(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.is_flow = true; @@ -847,7 +845,7 @@ mod schedule_push { // try_schedule_next_job: script with retry wraps in SingleStepFlow // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_try_schedule_with_retry(db: Pool) -> anyhow::Result<()> { let schedule = make_schedule(|s| { s.retry = Some(serde_json::json!({ @@ -882,7 +880,7 @@ mod schedule_push { // try_schedule_next_job: push failure error message is stored on schedule // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_failure_stores_error_message(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -927,7 +925,7 @@ mod schedule_push { // try_schedule_next_job: disabled schedule leaves no side effects // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_disabled_schedule_no_side_effects(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -970,7 +968,7 @@ mod schedule_push { // try_schedule_next_job: path mismatch leaves schedule unchanged // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_path_mismatch_no_side_effects(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1012,7 +1010,7 @@ mod schedule_push { // doesn't error. The function should return (tx, None). // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_failure_schedule_not_in_db(db: Pool) -> anyhow::Result<()> { // Do NOT insert a schedule row — the disable UPDATE will match 0 rows let schedule = make_schedule(|s| { @@ -1044,7 +1042,7 @@ mod schedule_push { // unmodified enabled state are committed together. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_invariant_success_means_tick_committed(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1089,7 +1087,7 @@ mod schedule_push { // enabled and no next tick. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_invariant_failure_means_disabled_after_commit( db: Pool, ) -> anyhow::Result<()> { @@ -1139,7 +1137,7 @@ mod schedule_push { // enabled so that zombie retry can re-attempt. // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_invariant_rollback_preserves_schedule_for_retry( db: Pool, ) -> anyhow::Result<()> { @@ -1187,6 +1185,7 @@ mod schedule_push { // Failpoint tests — feature-gated, only compiled under `failpoints` // =================================================================== + #[cfg(feature = "failpoints")] mod failpoint_tests { use super::*; use windmill_queue::jobs::schedule_failpoints::{ScheduleFailPoint, ACTIVE}; @@ -1195,7 +1194,7 @@ mod schedule_push { // SavepointCreate failpoint → schedule disabled, 0 jobs // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_savepoint_create_disables(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1232,7 +1231,7 @@ mod schedule_push { // Push failpoint → schedule disabled, 0 jobs // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_push_disables(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1269,7 +1268,7 @@ mod schedule_push { // SavepointCommit failpoint → schedule disabled, 0 jobs // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_savepoint_commit_disables(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1306,7 +1305,7 @@ mod schedule_push { // ScheduleDisable failpoint → returns Some(err), caller doesn't commit // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_schedule_disable_returns_err(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1335,7 +1334,7 @@ mod schedule_push { // ScheduleDisable failpoint + tx drop → schedule stays enabled // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_disable_failure_rollback(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1375,7 +1374,7 @@ mod schedule_push { // no error handler notification (QuotaExceeded is silenced) // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_push_quota_exceeded_script(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1414,7 +1413,7 @@ mod schedule_push { // PushQuotaExceeded failpoint (flow) → schedule disabled, 0 jobs // --------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_push_quota_exceeded_flow(db: Pool) -> anyhow::Result<()> { sqlx::query( "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) @@ -1470,7 +1469,7 @@ mod schedule_push { // 4. The schedule remains enabled for retry on next flow execution // ----------------------------------------------------------------------- - #[sqlx::test(fixtures("base", "schedule_push"))] + #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_zombie_flow_after_schedule_push_failure_meets_restart_criteria( db: Pool, ) -> anyhow::Result<()> { diff --git a/backend/windmill-test-utils/Cargo.toml b/backend/windmill-test-utils/Cargo.toml new file mode 100644 index 0000000000..3d253a6772 --- /dev/null +++ b/backend/windmill-test-utils/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "windmill-test-utils" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_test_utils" +path = "src/lib.rs" + +[features] +default = [] +private = [] +enterprise = [] +python = ["windmill-common/python"] +deno_core = ["dep:windmill-runtime-nativets"] +agent_worker_server = ["dep:windmill-api-agent-workers"] + +[dependencies] +windmill-api = { workspace = true, default-features = false } +windmill-worker.workspace = true +windmill-queue.workspace = true +windmill-common = { workspace = true, default-features = false } +windmill-api-client.workspace = true +windmill-runtime-nativets = { workspace = true, optional = true } +windmill-api-agent-workers = { workspace = true, optional = true } + +sqlx.workspace = true +futures.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +uuid.workspace = true +chrono.workspace = true +axum.workspace = true +anyhow.workspace = true +tracing.workspace = true diff --git a/backend/tests/common/mod.rs b/backend/windmill-test-utils/src/lib.rs similarity index 99% rename from backend/tests/common/mod.rs rename to backend/windmill-test-utils/src/lib.rs index e377df5164..34858fac2e 100644 --- a/backend/tests/common/mod.rs +++ b/backend/windmill-test-utils/src/lib.rs @@ -574,7 +574,6 @@ pub async fn test_for_versions>( use futures::StreamExt; -// #[cfg(feature = "python")] pub async fn assert_lockfile( db: &Pool, script_content: String, From b11d6ed7940faddfe74a22b25bcb132527cbcec8 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 15:27:50 +0100 Subject: [PATCH 12/93] fix(frontend): revert CloseButton refactor that broke tag removal in MultiSelect (#7909) The recent refactor of CloseButton (from on:close component events to onClick prop) broke tag removal in MultiSelect/TagsToListenTo. The refactor changed on:pointerdown (component event) to onPointerdown (native DOM event), which stopped native pointerdown propagation and broke the drag tracking in DraggableTags, causing the dropdown to open on every close button click. Reverts CloseButton and all callers back to using createEventDispatcher and on:close. Co-authored-by: Claude Opus 4.6 --- frontend/src/lib/components/WorkerGroup.svelte | 2 +- .../apps/editor/settingsPanel/CSSMigrationModal.svelte | 2 +- .../apps/editor/settingsPanel/GridNavbar.svelte | 2 +- .../apps/editor/settingsPanel/GridTab.svelte | 2 +- .../apps/editor/settingsPanel/InputsSpecsEditor.svelte | 2 +- .../apps/editor/settingsPanel/TableActions.svelte | 2 +- .../settingsPanel/secondaryMenu/SecondaryMenu.svelte | 2 +- frontend/src/lib/components/common/CloseButton.svelte | 9 ++++++--- .../src/lib/components/common/button/Button.svelte | 2 +- .../lib/components/common/drawer/DrawerContent.svelte | 2 +- frontend/src/lib/components/common/modal/Modal.svelte | 2 +- .../lib/components/common/seconds/SecondsInput.svelte | 10 +++++----- .../src/lib/components/select/DraggableTags.svelte | 2 +- frontend/src/lib/components/select/MultiSelect.svelte | 2 +- frontend/src/lib/components/select/Select.svelte | 2 +- .../workspaceSettings/DataTableSettings.svelte | 2 +- .../workspaceSettings/DucklakeSettings.svelte | 2 +- .../workspaceSettings/StorageSettings.svelte | 2 +- 18 files changed, 27 insertions(+), 24 deletions(-) diff --git a/frontend/src/lib/components/WorkerGroup.svelte b/frontend/src/lib/components/WorkerGroup.svelte index 2637d54208..da3225df00 100644 --- a/frontend/src/lib/components/WorkerGroup.svelte +++ b/frontend/src/lib/components/WorkerGroup.svelte @@ -372,7 +372,7 @@ label: 'Reset to all tags minus native ones', onClick: () => { if (nconfig != undefined) { - nconfig.worker_tags = defaultTags.concat(nativeTags) + nconfig.worker_tags = defaultTags } }, disabled: !canEditConfig, diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/CSSMigrationModal.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/CSSMigrationModal.svelte index 2b4ea2b6bd..f4250d44ae 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/CSSMigrationModal.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/CSSMigrationModal.svelte @@ -157,7 +157,7 @@ >
Migrate to CSS editor
(migrationModalOpen = false)} + on:close={() => (migrationModalOpen = false)} />
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/GridNavbar.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/GridNavbar.svelte index d530d0b204..80e78020b3 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/GridNavbar.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/GridNavbar.svelte @@ -164,7 +164,7 @@ { + on:close={() => { items = items.filter((_, i) => i !== index) }} /> diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte index 28e5316146..b1111244db 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte @@ -205,7 +205,7 @@ bind:value={items[index].value} />
- deleteSubgrid(index)} /> + deleteSubgrid(index)} />
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte index e3ed15992c..19dd309bec 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte @@ -104,7 +104,7 @@ /> {#if deletable}
- dispatch('delete', k)} /> + dispatch('delete', k)} />
{/if} {/if} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/TableActions.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/TableActions.svelte index 3a121d7e61..17b301d58c 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/TableActions.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/TableActions.svelte @@ -195,7 +195,7 @@
deleteComponent(component.id, item.originalIndex)} + on:close={() => deleteComponent(component.id, item.originalIndex)} />
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte index 1e94952c4a..877c48bf35 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte @@ -29,7 +29,7 @@
- secondaryMenu?.close()} /> + secondaryMenu?.close()} /> {#if $secondaryMenu?.props?.type === 'style'}
Style Panel
diff --git a/frontend/src/lib/components/common/CloseButton.svelte b/frontend/src/lib/components/common/CloseButton.svelte index 1387049ee6..d7d0a58a7b 100644 --- a/frontend/src/lib/components/common/CloseButton.svelte +++ b/frontend/src/lib/components/common/CloseButton.svelte @@ -1,4 +1,5 @@
{title ?? ''} diff --git a/frontend/src/lib/components/common/modal/Modal.svelte b/frontend/src/lib/components/common/modal/Modal.svelte index 806795a286..ee1e062eb2 100644 --- a/frontend/src/lib/components/common/modal/Modal.svelte +++ b/frontend/src/lib/components/common/modal/Modal.svelte @@ -90,7 +90,7 @@ {style} > {#if kind == 'X'} -
(open = false)} />
(open = false)} />
{/if}
diff --git a/frontend/src/lib/components/common/seconds/SecondsInput.svelte b/frontend/src/lib/components/common/seconds/SecondsInput.svelte index d7b50aeb91..9ca42379d4 100644 --- a/frontend/src/lib/components/common/seconds/SecondsInput.svelte +++ b/frontend/src/lib/components/common/seconds/SecondsInput.svelte @@ -142,7 +142,7 @@ onblur={handleBlur} /> {day !== 1 ? 'days' : 'day'}{day && day > 1 ? 'days' : 'day'}
@@ -166,7 +166,7 @@ onblur={handleBlur} /> {hour !== 1 ? 'hrs' : 'hr'}{hour && hour > 1 ? 'hrs' : 'hr'}
@@ -190,7 +190,7 @@ onblur={handleBlur} /> {min !== 1 ? 'mins' : 'min'}{min && min > 1 ? 'mins' : 'min'}
@@ -214,7 +214,7 @@ onblur={handleBlur} /> {sec !== 1 ? 'secs' : 'sec'}{sec && sec > 1 ? 'secs' : 'sec'}
@@ -223,7 +223,7 @@ class="bg-transparent text-secondary hover:text-primary" noBg small - onClick={() => { + on:close={() => { seconds = defaultValue }} /> diff --git a/frontend/src/lib/components/select/DraggableTags.svelte b/frontend/src/lib/components/select/DraggableTags.svelte index fc6a5838b8..adb6f49e52 100644 --- a/frontend/src/lib/components/select/DraggableTags.svelte +++ b/frontend/src/lib/components/select/DraggableTags.svelte @@ -56,7 +56,7 @@ { e.stopPropagation(); onRemove(item) }} + on:close={(e) => (onRemove(item), e.stopPropagation())} /> {/if} diff --git a/frontend/src/lib/components/select/MultiSelect.svelte b/frontend/src/lib/components/select/MultiSelect.svelte index 57d2b6d7c6..6de40aa235 100644 --- a/frontend/src/lib/components/select/MultiSelect.svelte +++ b/frontend/src/lib/components/select/MultiSelect.svelte @@ -158,7 +158,7 @@ noBg class="ml-2 remove-all bg-transparent text-hint" small - onClick={(e) => { e.stopPropagation(); clearValue() }} + on:close={(e) => (clearValue(), e.stopPropagation())} /> {/if} {:else if RightIcon} diff --git a/frontend/src/lib/components/workspaceSettings/DataTableSettings.svelte b/frontend/src/lib/components/workspaceSettings/DataTableSettings.svelte index 42ed0c6eef..50ca7b2436 100644 --- a/frontend/src/lib/components/workspaceSettings/DataTableSettings.svelte +++ b/frontend/src/lib/components/workspaceSettings/DataTableSettings.svelte @@ -280,7 +280,7 @@ {/if} - removeDataTable(dataTableIndex)} /> + removeDataTable(dataTableIndex)} /> {/each} diff --git a/frontend/src/lib/components/workspaceSettings/DucklakeSettings.svelte b/frontend/src/lib/components/workspaceSettings/DucklakeSettings.svelte index 041817ba05..a7e26228e4 100644 --- a/frontend/src/lib/components/workspaceSettings/DucklakeSettings.svelte +++ b/frontend/src/lib/components/workspaceSettings/DucklakeSettings.svelte @@ -378,7 +378,7 @@ - removeDucklake(ducklakeIndex)} /> + removeDucklake(ducklakeIndex)} /> {/each} diff --git a/frontend/src/lib/components/workspaceSettings/StorageSettings.svelte b/frontend/src/lib/components/workspaceSettings/StorageSettings.svelte index 9af70ce88d..0ce937bd9b 100644 --- a/frontend/src/lib/components/workspaceSettings/StorageSettings.svelte +++ b/frontend/src/lib/components/workspaceSettings/StorageSettings.svelte @@ -227,7 +227,7 @@ {#if tableRow[0] !== null} { + on:close={() => { if (s3ResourceSettings.secondaryStorage) { s3ResourceSettings.secondaryStorage.splice(idx - 1, 1) s3ResourceSettings.secondaryStorage = [...s3ResourceSettings.secondaryStorage] From c9d5c24c34cef6bfc412943e5b1c339c71a60283 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 15:43:13 +0100 Subject: [PATCH 13/93] chore(main): release 1.631.2 (#7906) * chore(main): release 1.631.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 9 ++ backend/Cargo.lock | 148 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 100 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e27a5ac29e..4b48754238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.631.2](https://github.com/windmill-labs/windmill/compare/v1.631.1...v1.631.2) (2026-02-11) + + +### Bug Fixes + +* **frontend:** revert CloseButton refactor that broke tag removal in MultiSelect ([#7909](https://github.com/windmill-labs/windmill/issues/7909)) ([b11d6ed](https://github.com/windmill-labs/windmill/commit/b11d6ed7940faddfe74a22b25bcb132527cbcec8)) +* nix flake libz.so for deno_core ([#7905](https://github.com/windmill-labs/windmill/issues/7905)) ([900c76c](https://github.com/windmill-labs/windmill/commit/900c76ccad09f58fc6adcfa8151db780249617f6)) +* strip unsupported schema fields for Google AI ([#7894](https://github.com/windmill-labs/windmill/issues/7894)) ([5effb87](https://github.com/windmill-labs/windmill/commit/5effb87a36793e20d17e678619091ef458fe8f0a)), closes [#7759](https://github.com/windmill-labs/windmill/issues/7759) + ## [1.631.1](https://github.com/windmill-labs/windmill/compare/v1.631.0...v1.631.1) (2026-02-11) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index bef3fe26f7..5cdf2ad90a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2322,9 +2322,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -2332,9 +2332,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -2356,9 +2356,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "clipboard-win" @@ -15725,7 +15725,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-nats", @@ -15786,7 +15786,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -15799,7 +15799,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "argon2", @@ -15936,7 +15936,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -15959,7 +15959,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -15972,7 +15972,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -15998,7 +15998,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.631.1" +version = "1.631.2" dependencies = [ "reqwest 0.12.28", "serde", @@ -16008,7 +16008,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16025,7 +16025,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16048,7 +16048,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -16071,7 +16071,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16087,7 +16087,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16107,7 +16107,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16127,7 +16127,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16141,7 +16141,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-nats", @@ -16161,7 +16161,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -16186,7 +16186,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "flate2", @@ -16202,7 +16202,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -16223,7 +16223,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16243,7 +16243,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16272,7 +16272,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -16298,7 +16298,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.631.1" +version = "1.631.2" dependencies = [ "lazy_static", "serde", @@ -16310,7 +16310,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.631.1" +version = "1.631.2" dependencies = [ "argon2", "axum 0.7.9", @@ -16333,7 +16333,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16347,7 +16347,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.631.1" +version = "1.631.2" dependencies = [ "axum 0.7.9", "chrono", @@ -16376,7 +16376,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.631.1" +version = "1.631.2" dependencies = [ "chrono", "lazy_static", @@ -16390,7 +16390,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -16409,7 +16409,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.631.1" +version = "1.631.2" dependencies = [ "aes-gcm", "anyhow", @@ -16508,7 +16508,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.631.1" +version = "1.631.2" dependencies = [ "chrono", "itertools 0.14.0", @@ -16527,7 +16527,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.631.1" +version = "1.631.2" dependencies = [ "regex", "serde", @@ -16542,7 +16542,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16566,7 +16566,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "futures", @@ -16583,7 +16583,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.631.1" +version = "1.631.2" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16599,7 +16599,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -16620,7 +16620,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -16651,7 +16651,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-oauth2", @@ -16675,7 +16675,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.631.1" +version = "1.631.2" dependencies = [ "convert_case 0.6.0", "serde", @@ -16684,7 +16684,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "lazy_static", @@ -16696,7 +16696,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "serde_json", @@ -16708,7 +16708,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "gosyn", @@ -16720,7 +16720,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "lazy_static", @@ -16732,7 +16732,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "serde_json", @@ -16744,7 +16744,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "nu-parser", @@ -16755,7 +16755,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16766,7 +16766,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16779,7 +16779,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-recursion", @@ -16803,7 +16803,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "lazy_static", @@ -16817,7 +16817,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16834,7 +16834,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "lazy_static", @@ -16848,7 +16848,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "lazy_static", @@ -16867,7 +16867,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "serde", @@ -16878,7 +16878,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-recursion", @@ -16915,7 +16915,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "const_format", @@ -16953,7 +16953,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.631.1" +version = "1.631.2" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -16963,7 +16963,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-recursion", @@ -16992,7 +16992,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "axum 0.7.9", @@ -17015,7 +17015,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17048,7 +17048,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17068,7 +17068,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17102,7 +17102,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17136,7 +17136,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17159,7 +17159,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17183,7 +17183,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-nats", @@ -17207,7 +17207,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17242,7 +17242,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17270,7 +17270,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-trait", @@ -17293,7 +17293,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "chrono", @@ -17310,7 +17310,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.631.1" +version = "1.631.2" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 9c22b21604..1719b527ba 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.631.1" +version = "1.631.2" authors.workspace = true edition.workspace = true @@ -74,7 +74,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.631.1" +version = "1.631.2" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 6e7752a2f5..532c495d34 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.631.1 + version: 1.631.2 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index b02ced7283..fdc042155d 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.631.1"; +export const VERSION = "v1.631.2"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 3274c35f2b..43a049e7bb 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -77,7 +77,7 @@ export { // } // }); -export const VERSION = "1.631.1"; +export const VERSION = "1.631.2"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 85513fbbb3..92d179cb05 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.631.1", + "version": "1.631.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.631.1", + "version": "1.631.2", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 045d4e8379..54daa4cb87 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.631.1", + "version": "1.631.2", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index fe626dda2d..e24a122700 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.631.1" -wmill_pg = ">=1.631.1" +wmill = ">=1.631.2" +wmill_pg = ">=1.631.2" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 23f68cfe94..5214cdccb8 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.631.1 + version: 1.631.2 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 132e472215..27df5cdc46 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.631.1' + ModuleVersion = '1.631.2' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 1179e9f656..dca6c0d1ab 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.631.1" +version = "1.631.2" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 6c4f58d66f..86ab74dd65 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.631.1" +version = "1.631.2" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index fbdb3b4697..48a04e9375 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.631.1", + "version": "1.631.2", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 0990b945cd..4f2fd622ac 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.631.1", + "version": "1.631.2", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 0f12a0973d..fc2487c8f4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.631.1 +1.631.2 From 4d94d808358398a7848498c9ca9eac3f840db756 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 15:08:22 +0000 Subject: [PATCH 14/93] add missing feature-gated deps to api-integration-tests crate Co-Authored-By: Claude Opus 4.6 --- backend/Cargo.lock | 4 ++++ backend/windmill-api-integration-tests/Cargo.toml | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5cdf2ad90a..1e99a32ae5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -16145,6 +16145,10 @@ version = "1.631.2" dependencies = [ "anyhow", "async-nats", + "aws-config", + "aws-credential-types", + "aws-sdk-sqs", + "base64 0.22.1", "rand 0.9.0", "rdkafka", "reqwest 0.13.1", diff --git a/backend/windmill-api-integration-tests/Cargo.toml b/backend/windmill-api-integration-tests/Cargo.toml index 8caa178d3d..4d5759335f 100644 --- a/backend/windmill-api-integration-tests/Cargo.toml +++ b/backend/windmill-api-integration-tests/Cargo.toml @@ -10,8 +10,8 @@ path = "src/lib.rs" [features] default = [] -private = ["windmill-test-utils/private"] -enterprise = ["windmill-test-utils/enterprise"] +private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential-types", "dep:aws-sdk-sqs"] +enterprise = ["windmill-test-utils/enterprise", "dep:base64"] [dependencies] windmill-test-utils.workspace = true @@ -28,3 +28,7 @@ rand.workspace = true rumqttc.workspace = true rdkafka.workspace = true async-nats.workspace = true +aws-config = { workspace = true, optional = true } +aws-credential-types = { workspace = true, optional = true } +aws-sdk-sqs = { workspace = true, optional = true } +base64 = { workspace = true, optional = true } From 44143e75762c6e721114454177d48bcc3f83ad19 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 11 Feb 2026 17:05:34 +0100 Subject: [PATCH 15/93] replace leftover common:: references in dependency_map test (#7911) * fix: replace leftover common:: references in dependency_map test Co-Authored-By: Claude Opus 4.6 * fix: add missing deno_core/mcp features and gate dead code in permissions test Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- backend/CLAUDE.md | 4 ++++ backend/tests/dependency_map.rs | 8 +++++--- backend/windmill-api-integration-tests/Cargo.toml | 2 ++ .../windmill-api-integration-tests/tests/permissions.rs | 6 ++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index dcc993b6c5..edfeb5207d 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -49,6 +49,10 @@ Windmill uses a workspace-based architecture with multiple crates: - Use feature flags: `#[cfg(feature = "enterprise")]` - Isolate enterprise code in separate modules +## Git Workflow + +- **Never push directly to main** — always create a branch and open a pull request + ## Testing - Write unit tests for core functionality diff --git a/backend/tests/dependency_map.rs b/backend/tests/dependency_map.rs index 9113e8f165..bb9510c688 100644 --- a/backend/tests/dependency_map.rs +++ b/backend/tests/dependency_map.rs @@ -2,7 +2,9 @@ use sqlx::{Pool, Postgres}; use tokio_stream::StreamExt; use windmill_api_client::types::NewScript; -use windmill_test_utils::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer}; +use windmill_test_utils::{ + in_test_worker, init_client, listen_for_completed_jobs, rebuild_dmap, ApiServer, +}; mod dependency_map { use super::*; @@ -169,7 +171,7 @@ mod dependency_map { let (client, _port, _s) = init(db.clone()).await; assert_dmap(&db, None, CORRECT_DMAP.clone()).await; // rebuild map - assert!(common::rebuild_dmap(&client).await); + assert!(rebuild_dmap(&client).await); assert_dmap(&db, None, CORRECT_DMAP.clone()).await; Ok(()) } @@ -182,7 +184,7 @@ mod dependency_map { // Spawn first rebuild let handle = { let client = client.clone(); - tokio::spawn(async move { common::rebuild_dmap(&client).await }) + tokio::spawn(async move { rebuild_dmap(&client).await }) }; // Immidiately spawn another diff --git a/backend/windmill-api-integration-tests/Cargo.toml b/backend/windmill-api-integration-tests/Cargo.toml index 4d5759335f..1936a13fd9 100644 --- a/backend/windmill-api-integration-tests/Cargo.toml +++ b/backend/windmill-api-integration-tests/Cargo.toml @@ -12,6 +12,8 @@ path = "src/lib.rs" default = [] private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential-types", "dep:aws-sdk-sqs"] enterprise = ["windmill-test-utils/enterprise", "dep:base64"] +deno_core = ["windmill-test-utils/deno_core"] +mcp = [] [dependencies] windmill-test-utils.workspace = true diff --git a/backend/windmill-api-integration-tests/tests/permissions.rs b/backend/windmill-api-integration-tests/tests/permissions.rs index 329ed5e11d..4342c61235 100644 --- a/backend/windmill-api-integration-tests/tests/permissions.rs +++ b/backend/windmill-api-integration-tests/tests/permissions.rs @@ -1,9 +1,13 @@ +#[cfg(feature = "deno_core")] use serde_json::json; +#[cfg(feature = "deno_core")] use sqlx::{Pool, Postgres}; +#[cfg(feature = "deno_core")] use windmill_test_utils::*; /// Helper to create a client authenticated as a specific user +#[cfg(feature = "deno_core")] async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client { let mut headers = reqwest::header::HeaderMap::new(); headers.insert( @@ -17,12 +21,14 @@ async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client { } /// Test helper to check if a GET request succeeds +#[cfg(feature = "deno_core")] async fn can_read(client: &reqwest::Client, url: &str) -> bool { let resp = client.get(url).send().await.unwrap(); resp.status().is_success() } /// Test helper to check if a POST request succeeds (for write operations) +#[cfg(feature = "deno_core")] async fn can_write(client: &reqwest::Client, url: &str, body: serde_json::Value) -> bool { let resp = client.post(url).json(&body).send().await.unwrap(); let status = resp.status(); From ef533adc5be2c7da986e0bb660a5ddc3a0732b7b Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:30:07 +0100 Subject: [PATCH 16/93] docs command (#7912) --- .github/workflows/git-commands.yaml | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/git-commands.yaml b/.github/workflows/git-commands.yaml index 6e44067283..b9602d122e 100644 --- a/.github/workflows/git-commands.yaml +++ b/.github/workflows/git-commands.yaml @@ -283,3 +283,34 @@ jobs: repo: context.repo.repo, body: 'Successfully updated ee-repo-ref.txt' }) + + update-docs: + if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') + runs-on: ubicloud-standard-2 + permissions: + contents: read + pull-requests: read + issues: read + steps: + - uses: actions/create-github-app-token@v2 + id: app + with: + app-id: ${{ vars.INTERNAL_APP_ID }} + private-key: ${{ secrets.INTERNAL_APP_KEY }} + owner: ${{ github.repository_owner }} + repositories: | + windmilldocs + + - name: Trigger docs update + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + COMMENT_TEXT: ${{ github.event.comment.body }} + run: | + jq -n \ + --argjson pr_number ${{ github.event.issue.number }} \ + --arg repo "${{ github.event.repository.name }}" \ + --arg comment "$COMMENT_TEXT" \ + '{event_type: "create-docs", client_payload: {pr_number: $pr_number, repo: $repo, comment_text: $comment}}' | \ + gh api repos/windmill-labs/windmilldocs/dispatches \ + --method POST \ + --input - From 238a5736504bcbfac53d07fd95750706950f3b90 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:30:19 +0100 Subject: [PATCH 17/93] remove claude from flake (#7913) --- flake.lock | 16 ---------------- flake.nix | 10 +--------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/flake.lock b/flake.lock index b1dcc73104..486771d582 100644 --- a/flake.lock +++ b/flake.lock @@ -33,21 +33,6 @@ "type": "indirect" } }, - "nixpkgs-claude": { - "locked": { - "lastModified": 1764517877, - "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" - } - }, "nixpkgs-oapi-gen": { "locked": { "lastModified": 1740303746, @@ -83,7 +68,6 @@ "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "nixpkgs-claude": "nixpkgs-claude", "nixpkgs-oapi-gen": "nixpkgs-oapi-gen", "rust-overlay": "rust-overlay" } diff --git a/flake.nix b/flake.nix index fc01be4166..92fca24cdd 100644 --- a/flake.nix +++ b/flake.nix @@ -3,12 +3,10 @@ nixpkgs.url = "nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay.url = "github:oxalica/rust-overlay"; - # Use separate channel for claude code. It always needs to be latest - nixpkgs-claude.url = "nixpkgs/nixos-unstable"; nixpkgs-oapi-gen.url = "nixpkgs/2d068ae5c6516b2d04562de50a58c682540de9bf"; # openapi-generator-cli pin to 7.10.0 }; - outputs = { self, nixpkgs, nixpkgs-claude, flake-utils, rust-overlay + outputs = { self, nixpkgs, flake-utils, rust-overlay , nixpkgs-oapi-gen }: flake-utils.lib.eachDefaultSystem (system: let @@ -17,10 +15,6 @@ config.allowUnfree = true; overlays = [ (import rust-overlay) ]; }; - claude-code = (import nixpkgs-claude { - inherit system; - config.allowUnfree = true; - }).claude-code; openapi-generator-cli = (import nixpkgs-oapi-gen { inherit system; }).openapi-generator-cli; @@ -141,8 +135,6 @@ devShells.default = pkgs.mkShell { buildInputs = buildInputs ++ [ - # To update run: `nix flake update nixpkgs-claude` - claude-code # To update run: `nix flake update nixpkgs-oapi-gen` openapi-generator-cli ] ++ (with pkgs; [ From f1ac7b6af9f9eda3e3b3f60184763c8d63abdf30 Mon Sep 17 00:00:00 2001 From: Henri Courdent <122811744+hcourdent@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:30:41 +0100 Subject: [PATCH 18/93] Team plans page clarifications & readability (#7910) * Team plans page clarifications & readability * Using SettingsPageHeader --- .../components/settings/PremiumInfo.svelte | 408 ++++++++++++------ 1 file changed, 275 insertions(+), 133 deletions(-) diff --git a/frontend/src/lib/components/settings/PremiumInfo.svelte b/frontend/src/lib/components/settings/PremiumInfo.svelte index 8683ca2356..27ead12646 100644 --- a/frontend/src/lib/components/settings/PremiumInfo.svelte +++ b/frontend/src/lib/components/settings/PremiumInfo.svelte @@ -7,10 +7,10 @@ import { workspaceStore } from '$lib/stores' import { Button } from '../common' import Tooltip from '../Tooltip.svelte' + import SettingsPageHeader from '$lib/components/settings/SettingsPageHeader.svelte' import { ExternalLink, Pen, X } from 'lucide-svelte' import Section from '../Section.svelte' import Range from '../Range.svelte' - import Label from '../Label.svelte' import Modal from '../common/modal/Modal.svelte' import { slide } from 'svelte/transition' @@ -71,12 +71,14 @@ const info = await WorkspaceService.getPremiumInfo({ workspace: $workspaceStore! }) const developerNb = users?.filter((x) => !x.operator)?.length ?? 0 const operatorNb = users?.filter((x) => x.operator)?.length ?? 0 + const usage = info.usage ?? 0 + const seatsFromUsers = Math.ceil(developerNb + operatorNb / 2) - const seatsFromExtraComps = Math.max(Math.ceil((info.usage ?? 0) / 10000) - seatsFromUsers, 0) + const seatsFromExtraComps = Math.max(Math.ceil(usage / 10000) - seatsFromUsers, 0) const usedSeats = seatsFromUsers + seatsFromExtraComps premiumInfo = { ...info, - usage: info.usage ?? 0, + usage, owner: info.owner, developerNb, operatorNb, @@ -112,9 +114,10 @@ } } - let estimatedDevs = 1 + let estimatedDevsRaw = 1 let estimatedOps = 0 + $: estimatedDevs = Math.max(1, estimatedDevsRaw) $: estimatedSeats = estimatedDevs + Math.ceil(estimatedOps / 2) let estimatedExecs = 1 @@ -125,6 +128,8 @@ } } $: estimatedSeats && updateExecs() + + const formatNumber = (value: number) => value.toLocaleString('en-US') @@ -150,20 +155,19 @@
-
- {#if premiumInfo?.premium && plan} - Plan: {capitalize(plan)} plan{plan === 'team' ? ' (usage-based)' : ''} - {:else} - Plan: Free plan - {/if} -
+ {#if premiumInfo?.status === 'past_due'}

{#if premiumInfo.max_tolerated_executions === undefined || premiumInfo.usage > premiumInfo.max_tolerated_executions} Your last invoice is unpaid, you cannot run any more jobs. Please update your payment - method in the customer portal to continue running jobs. + method in the Customer Portal to continue running jobs. {:else} - Your last invoice is unpaid. Please update your payment method in the customer portal to + Your last invoice is unpaid. Please update your payment method in the Customer Portal to prevent the interruption of your job executions. {/if}

@@ -238,78 +242,125 @@ - Developers - - Actual pricing is calculated on the MAXIMUM number of users in a given billing - period, see the customer portal for more info. - +
+
Developers
+

+ Calculated on the MAXIMUM number of users in a given billing + period, see the Customer Portal for more info. +

+
-
- {premiumInfo.developerNb} +
+ {formatNumber(premiumInfo.developerNb)}
- Operators - - Actual pricing is calculated on the MAXIMUM number of operators in a given - billing period, see the customer portal for more info. - +
+
Operators
+

+ Calculated on the MAXIMUM number of operators in a given + billing period, see the Customer Portal for more info. +

+
-
- {premiumInfo.operatorNb} +
+ {formatNumber(premiumInfo.operatorNb)} +
+ + + + +
+
Seats from users
+

+ 1 developer = 1 seat, 2 operators = 1 seat. +

+

+ u = ceil({formatNumber(premiumInfo.developerNb)} + {formatNumber(premiumInfo.operatorNb)}/2) + = {formatNumber(premiumInfo.seatsFromUsers)} +

+
+
+ +
+ {formatNumber(premiumInfo.seatsFromUsers)}
-
Number of seats for users
+ +
+
Executions this month
+

+ One execution equals one job + up to 1 second on a worker with 2GB of memory, with each additional + second counting as an extra execution. +

+
+
-
- u = ceil({premiumInfo.developerNb} + {premiumInfo.operatorNb}/2) = {premiumInfo.seatsFromUsers} +
+ {formatNumber(premiumInfo.usage)}
- Computations executed this month + +
+
Included executions from user seats
+

+ Each seat includes 10,000 executions per month. +

+
+
-
- {premiumInfo.usage} +
+ - {formatNumber(premiumInfo.seatsFromUsers * 10000)}
- - Included computations with users (10k per user seat) + + +
+
Extra seats from computations
+

+ Additional seats created by executions beyond the included quota. +

+

+ c = ceil(max(0, {formatNumber(premiumInfo.usage)} - + {formatNumber(premiumInfo.seatsFromUsers * 10000)}) / 10,000) = + {formatNumber(premiumInfo.seatsFromExtraComps)} +

+
+
-
- - {premiumInfo.seatsFromUsers * 10000} +
+ {formatNumber(premiumInfo.seatsFromExtraComps)}
- -
Number of seats for extra computations
- -
- c = ceil({Math.max(0, premiumInfo.usage - premiumInfo.seatsFromUsers * 10000)} / - 10 000) = {premiumInfo.seatsFromExtraComps} + + +
+
+ Used seats (billed) +
+

+ Highest between seats from 'Developers + Operators' and 'Seats from executions'. + This is the number of seats used for billing this month. +

+

+ u + c = {formatNumber(premiumInfo.usedSeats)} +

- - -
Used seats Highest between seats from developers + operators and seats from computations -
-
- u + c = {premiumInfo.usedSeats} +
+ {formatNumber(premiumInfo.usedSeats)}
@@ -328,68 +379,145 @@
-
- - - -
-
-
Seats for users = {estimatedDevs} devs + {estimatedOps} ops / 2
-
{pluralize(estimatedSeats, 'seat')}
-
-
-
Extra computations = {estimatedExecs * 10}k execs - {estimatedSeats * 10}k included with - users
-
{(estimatedExecs - estimatedSeats) * 10}k extra execs = {pluralize( - estimatedExecs - estimatedSeats, - 'seat' - )}
-
-
-
Total
-
-
- {pluralize(estimatedSeats + (estimatedExecs - estimatedSeats), 'seat')} = {(estimatedSeats + - (estimatedExecs - estimatedSeats)) * - 10}$ / month +
+
+
Estimate your monthly cost
+

+ This is a rough estimate based on your expected team size and workload. Actual billing is based + on the maximum number of users and executions in a given month. +

+
+ +
+ +
+
+
+
Developers
+
+ {estimatedDevs} dev{estimatedDevs === 1 ? '' : 's'} +
+ +
+ +
+
+
Operators
+
+ {estimatedOps} operator{estimatedOps === 1 ? '' : 's'} +
+
+ +

+ 2 operators = 1 seat +

+
+ +
+
+
+
Monthly executions
+ + One execution equals one job up to 1 second on a virtual CPU with 2 GB of memory, with + each additional second counting as an extra execution. + +
+
+ {estimatedExecs * 10}k executions / month +
+
+ `${v * 10}k`} + hideInput + /> +

+ Each seat includes 10k executions per month. +

+
+
+ + + {#if estimatedSeats} +
+
+
Seats from users
+
+ {pluralize(estimatedSeats, 'seat')} +
+
+ +
+
+ Based on {estimatedDevs} dev{estimatedDevs === 1 ? '' : 's'} and + {` ${estimatedOps} operator${estimatedOps === 1 ? '' : 's'}`} +
+
+ +
+ +
+
Included executions
+
{estimatedSeats * 10}k
+
+ +
+
Extra executions
+
+ {(estimatedExecs - estimatedSeats) * 10}k +
+
+ +
+
Charged as additional seats
+
+ ≈ {pluralize(estimatedExecs - estimatedSeats, 'seat')} +
+
+ +
+ +
+
Total estimated seats
+
+
+ {pluralize(estimatedSeats + (estimatedExecs - estimatedSeats), 'seat')} +
+
+ {(estimatedSeats + (estimatedExecs - estimatedSeats)) * 10}$ / month +
+
+
+ {#if premiumInfo?.premium && plan === 'team'} {/if} + +
+ {#if (estimatedSeats + (estimatedExecs - estimatedSeats)) * 10 > 700} + + Higher usage? Explore Cloud Enterprise + + {/if} +
-
- {#if (estimatedSeats + (estimatedExecs - estimatedSeats)) * 10 > 700} - - You should consider subscribing to Cloud Enterprise - {/if}
@@ -410,15 +538,25 @@ {planTitle}
    - {#each planDesc as item} -
  • {@html item}
  • + {#each planDesc as item, i} + {#if planTitle === 'Team' && i === 1} +
  • + Every seat includes 10 000 executions + + One execution equals one job up to 1 second on a virtual CPU with 2 GB of memory, with + each additional second counting as an extra execution. + +
  • + {:else} +
  • {@html item}
  • + {/if} {/each}
- {#if planTitle == 'Team'} - {#if plan != 'team'} -
+
+ {#if planTitle == 'Team'} + {#if plan != 'team'} {#if plan != 'enterprise'} -
- {:else} -
Workspace is on enterprise plan
+ {:else} +
+ Workspace is on enterprise plan +
+ {/if} + {:else if planTitle === 'Free'} + {#if plan} +
+ Cancel your plan in the Customer Portal to downgrade to the free plan +
+ {:else} +
+ Workspace is on the free plan +
+ {/if} {/if} - {:else if planTitle === 'Free'} - {#if plan} -
- Cancel your plan in the customer portal to downgrade to the free plan -
- {:else} -
Workspace is on the free plan
- {/if} - {/if} +
{/each}
From 22f22c26612b904c2b82b415ec9337213ef593c3 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Wed, 11 Feb 2026 14:27:42 -0500 Subject: [PATCH 19/93] fix: add kafka kerberos runtime packages (#7918) --- Dockerfile | 2 +- docker/RHEL8/Dockerfile | 2 +- docker/RHEL9/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7b51669543..6fa79b12b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -149,7 +149,7 @@ ENV PATH /usr/local/bin:/root/.local/bin:/tmp/.local/bin:$PATH RUN apt-get update \ - && apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common tini libsasl2-modules-gssapi-mit \ + && apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common tini libsasl2-modules-gssapi-mit krb5-user \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docker/RHEL8/Dockerfile b/docker/RHEL8/Dockerfile index 6832e88625..6918ece6e0 100644 --- a/docker/RHEL8/Dockerfile +++ b/docker/RHEL8/Dockerfile @@ -64,7 +64,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel cyrus-sasl-devel cyrus-sasl-gssapi libcurl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel krb5-workstation cyrus-sasl-devel cyrus-sasl-gssapi libcurl-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json diff --git a/docker/RHEL9/Dockerfile b/docker/RHEL9/Dockerfile index 7bfeb48eff..31c474af77 100644 --- a/docker/RHEL9/Dockerfile +++ b/docker/RHEL9/Dockerfile @@ -64,7 +64,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel cyrus-sasl-devel cyrus-sasl-gssapi libcurl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel krb5-workstation cyrus-sasl-devel cyrus-sasl-gssapi libcurl-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json From aaa1b92300bdc0794de5356ef6750bab5d8d81a0 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 11 Feb 2026 20:29:44 +0100 Subject: [PATCH 20/93] fix: hash long dedicated worker tags (#7914) * fix: hash long dedicated worker tags * Update frontend/src/lib/components/dedicated_worker.ts Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- backend/windmill-api-scripts/src/scripts.rs | 32 +++++--- backend/windmill-api/src/jobs.rs | 6 +- backend/windmill-common/src/worker.rs | 80 ++++++++++++++++++- backend/windmill-queue/src/jobs.rs | 19 ++--- .../DedicatedWorkersSelector.svelte | 78 +++++++++--------- .../src/lib/components/ScriptBuilder.svelte | 4 +- .../src/lib/components/WorkerGroup.svelte | 40 +++++++--- .../src/lib/components/dedicated_worker.ts | 41 ++++++++++ .../flows/content/FlowSettings.svelte | 7 +- 9 files changed, 225 insertions(+), 82 deletions(-) create mode 100644 frontend/src/lib/components/dedicated_worker.ts diff --git a/backend/windmill-api-scripts/src/scripts.rs b/backend/windmill-api-scripts/src/scripts.rs index 96968605b1..85ec0edae4 100644 --- a/backend/windmill-api-scripts/src/scripts.rs +++ b/backend/windmill-api-scripts/src/scripts.rs @@ -12,7 +12,10 @@ use windmill_api_auth::{ check_scopes, maybe_refresh_folders, require_owner_of_path, ApiAuthed, }; use windmill_common::{ - utils::{BulkDeleteRequest, WithStarredInfoQuery, HTTP_CLIENT}, webhook::{WebhookMessage, WebhookShared}, workspaces::{check_user_against_rule, ProtectionRuleKind, RuleCheckResult}, DB + utils::{BulkDeleteRequest, WithStarredInfoQuery, HTTP_CLIENT}, + webhook::{WebhookMessage, WebhookShared}, + workspaces::{check_user_against_rule, ProtectionRuleKind, RuleCheckResult}, + DB, }; use windmill_queue::schedule::clear_schedule; @@ -859,10 +862,13 @@ async fn create_script_internal<'c>( } }; - let runnable_settings_handle = windmill_common::runnable_settings::insert_rs(RunnableSettings { - debouncing_settings: ns.debouncing_settings.insert_cached(&db).await?, - concurrency_settings: ns.concurrency_settings.insert_cached(&db).await?, - }, &db) + let runnable_settings_handle = windmill_common::runnable_settings::insert_rs( + RunnableSettings { + debouncing_settings: ns.debouncing_settings.insert_cached(&db).await?, + concurrency_settings: ns.concurrency_settings.insert_cached(&db).await?, + }, + &db, + ) .await?; let ( @@ -1072,7 +1078,9 @@ async fn create_script_internal<'c>( } if needs_lock_gen { let tag = if ns.dedicated_worker.is_some_and(|x| x) { - Some(format!("{}:{}", &w_id, &ns.path,)) + Some(windmill_common::worker::dedicated_worker_tag( + &w_id, &ns.path, + )) } else if ns.tag.as_ref().is_some_and(|x| x.contains("$args[")) { None } else { @@ -1839,7 +1847,9 @@ async fn get_script_by_hash( tx.commit().await?; - Ok(Json(windmill_common::scripts::prefetch_cached_script_with_starred(r, &db).await?)) + Ok(Json( + windmill_common::scripts::prefetch_cached_script_with_starred(r, &db).await?, + )) } async fn raw_script_by_hash( @@ -2042,7 +2052,9 @@ async fn archive_script_by_hash( WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() }, ); - Ok(Json(windmill_common::scripts::prefetch_cached_script(script, &db).await?)) + Ok(Json( + windmill_common::scripts::prefetch_cached_script(script, &db).await?, + )) } async fn delete_script_by_hash( @@ -2097,7 +2109,9 @@ async fn delete_script_by_hash( WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() }, ); - Ok(Json(windmill_common::scripts::prefetch_cached_script(script, &db).await?)) + Ok(Json( + windmill_common::scripts::prefetch_cached_script(script, &db).await?, + )) } #[derive(Deserialize)] diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index b2cc8623d1..fbd9a9b682 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -2135,9 +2135,7 @@ pub async fn resume_suspended_flow_as_owner( // Check approval conditions (self-approval, required groups, etc.) if let Some(ref flow_status_value) = flow.flow_status { - if let Ok(flow_status) = - serde_json::from_value::(flow_status_value.clone()) - { + if let Ok(flow_status) = serde_json::from_value::(flow_status_value.clone()) { let trigger_email = flow.email.as_deref().unwrap_or(""); conditionally_require_authed_user(Some(authed.clone()), flow_status, trigger_email)?; } @@ -5224,7 +5222,7 @@ async fn add_batch_jobs( let tag = if let Some(dedicated_worker) = dedicated_worker { if dedicated_worker && path.is_some() { - format!("{}:{}", w_id, path.clone().unwrap()) + windmill_common::worker::dedicated_worker_tag(&w_id, &path.clone().unwrap()) } else { format!("{}", language.as_str()) } diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 4a12ad00a4..6d392fa950 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -1560,6 +1560,24 @@ pub async fn update_worker_ping_main_loop_query( // occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus), // memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", +const MAX_TAG_LEN: usize = 50; +const HASH_SUFFIX_LEN: usize = 16; + +pub fn dedicated_worker_tag(workspace_id: &str, path: &str) -> String { + let full_tag = format!("{}:{}", workspace_id, path); + if full_tag.len() <= MAX_TAG_LEN { + return full_tag; + } + let hash = ::digest(full_tag.as_bytes()); + let hex_hash = hex::encode(hash); + let prefix_len = MAX_TAG_LEN - 1 - HASH_SUFFIX_LEN; + format!( + "{}#{}", + &full_tag[..prefix_len], + &hex_hash[..HASH_SUFFIX_LEN] + ) +} + pub async fn load_worker_config( db: &DB, killpill_tx: KillpillSender, @@ -1671,7 +1689,7 @@ pub async fn load_worker_config( if let Some(ref dws) = dedicated_workers.as_ref() { let mut dedi_tags: Vec = dws .iter() - .map(|dw| format!("{}:{}", dw.workspace_id, dw.path)) + .map(|dw| dedicated_worker_tag(&dw.workspace_id, &dw.path)) .collect(); if std::env::var("ADD_FLOW_TAG").is_ok() { dedi_tags.push("flow".to_string()); @@ -1679,9 +1697,9 @@ pub async fn load_worker_config( Some(dedi_tags) } else if let Some(ref dedicated_worker) = dedicated_worker.as_ref() { // Fallback to single dedicated worker for backward compatibility - let mut dedi_tags = vec![format!( - "{}:{}", - dedicated_worker.workspace_id, dedicated_worker.path + let mut dedi_tags = vec![dedicated_worker_tag( + &dedicated_worker.workspace_id, + &dedicated_worker.path, )]; if std::env::var("ADD_FLOW_TAG").is_ok() { dedi_tags.push("flow".to_string()); @@ -2178,4 +2196,58 @@ mod tests { result.sort(); assert_eq!(result, vec!["foo", "legacy(^ws1^ws2)", "urgent(ws1+ws2)"]); } + + #[test] + fn test_dedicated_worker_tag_short() { + let tag = dedicated_worker_tag("demo", "u/alice/script"); + assert_eq!(tag, "demo:u/alice/script"); + assert!(tag.len() <= MAX_TAG_LEN); + } + + #[test] + fn test_dedicated_worker_tag_exactly_50() { + // 50 chars exactly should not be hashed + let workspace = "ws"; + let path = "a".repeat(50 - workspace.len() - 1); // -1 for ':' + let tag = dedicated_worker_tag(workspace, &path); + assert_eq!(tag.len(), 50); + assert!(!tag.contains('#')); + } + + #[test] + fn test_dedicated_worker_tag_long_is_hashed() { + let tag = dedicated_worker_tag( + "my_workspace", + "u/engineering/team/automation/critical_workflow_script_v2", + ); + assert_eq!(tag.len(), MAX_TAG_LEN); + assert_eq!(tag, "my_workspace:u/engineering/team/a#5bc26db79926d4f0"); + } + + #[test] + fn test_dedicated_worker_tag_deterministic() { + let a = dedicated_worker_tag( + "ws", + "some/very/long/path/that/exceeds/the/fifty/char/limit/easily", + ); + assert_eq!(a, "ws:some/very/long/path/that/excee#bbb038d4268a0b41"); + let b = dedicated_worker_tag( + "ws", + "some/very/long/path/that/exceeds/the/fifty/char/limit/easily", + ); + assert_eq!(a, b); + } + + #[test] + fn test_dedicated_worker_tag_different_paths_differ() { + let a = dedicated_worker_tag( + "ws", + "some/very/long/path/that/exceeds/the/fifty/char/limit/easily_a", + ); + let b = dedicated_worker_tag( + "ws", + "some/very/long/path/that/exceeds/the/fifty/char/limit/easily_b", + ); + assert_ne!(a, b); + } } diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index b298fed82c..eaa75f04c1 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -5274,16 +5274,17 @@ async fn push_inner<'c, 'd>( .unwrap_or_else(|| (None, None)); let tag = if dedicated_worker.is_some_and(|x| x) { - format!( - "{}:{}{}", - workspace_id, - if job_kind == JobKind::Flow || job_kind == JobKind::FlowDependencies { - "flow/" - } else { - "" - }, + let flow_prefix = if job_kind == JobKind::Flow || job_kind == JobKind::FlowDependencies { + "flow/" + } else { + "" + }; + let full_path = format!( + "{}{}", + flow_prefix, runnable_path.clone().expect("dedicated script has a path") - ) + ); + windmill_common::worker::dedicated_worker_tag(workspace_id, &full_path) } else { if tag == Some("".to_string()) { tag = None; diff --git a/frontend/src/lib/components/DedicatedWorkersSelector.svelte b/frontend/src/lib/components/DedicatedWorkersSelector.svelte index df37b5bf17..336c2f263b 100644 --- a/frontend/src/lib/components/DedicatedWorkersSelector.svelte +++ b/frontend/src/lib/components/DedicatedWorkersSelector.svelte @@ -8,6 +8,7 @@ import { SvelteMap } from 'svelte/reactivity' import { untrack } from 'svelte' import BarsStaggered from './icons/BarsStaggered.svelte' + import { parseTag } from './dedicated_worker' // A "Runnable" is a script or flow with dedicated_worker=true interface Runnable { @@ -62,21 +63,6 @@ // Languages that support dedicated workers const DEDICATED_WORKER_LANGUAGES = ['python3', 'bun', 'deno'] - // Parse a tag to extract workspace, type (script/flow), and path - function parseTag(tag: string): { workspace: string; type: 'script' | 'flow'; path: string } | null { - const colonIndex = tag.indexOf(':') - if (colonIndex === -1) return null - - const workspace = tag.substring(0, colonIndex) - const rest = tag.substring(colonIndex + 1) - - if (rest.startsWith('flow/')) { - return { workspace, type: 'flow', path: rest.substring(5) } - } else { - return { workspace, type: 'script', path: rest } - } - } - // Resolve workspace script languages and filter to supported languages async function resolveAndFilterRunners( workspace: string, @@ -438,25 +424,27 @@ {:else}
{/if} -
- {#if info} - {#if info.type === 'flow'} - +
+
+ {#if info} + {#if info.type === 'flow'} + + {:else} + + {/if} + {info.path} + ({info.workspace}) + {#if info.type === 'flow' && info.runners} + + {info.runners.length} runner{info.runners.length !== 1 ? 's' : ''} + + {:else if info.type === 'script'} + 1 runner + {/if} {:else} - + {tag} {/if} - {info.path} - ({info.workspace}) - {#if info.type === 'flow' && info.runners} - - {info.runners.length} runner{info.runners.length !== 1 ? 's' : ''} - - {:else if info.type === 'script'} - 1 runner - {/if} - {:else} - {tag} - {/if} +
{#if !disabled}
- {runnable.displayName} + {runnable.displayName} {#if runnable.type === 'flow' && runnable.runners} - + {runnable.runners.length} {/if} @@ -614,11 +608,13 @@ {:else} {#each runnable.runners as runner (runner.stepId)}
- {runner.stepId} + {runner.stepId} {#if runner.stepSummary} - + {runner.stepSummary} {/if} diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index e2d1327eac..ca3c47ef20 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -1443,8 +1443,8 @@ {#if script.dedicated_worker}
- One worker in a worker group needs to be configured with dedicated worker - set to:
{$workspaceStore}:{script.path}
+ A worker group needs to be configured to listen to this script. Select + it in the dedicated workers section of the worker group configuration.
{/if} diff --git a/frontend/src/lib/components/WorkerGroup.svelte b/frontend/src/lib/components/WorkerGroup.svelte index da3225df00..820f21599e 100644 --- a/frontend/src/lib/components/WorkerGroup.svelte +++ b/frontend/src/lib/components/WorkerGroup.svelte @@ -43,6 +43,7 @@ import Dropdown from './DropdownV2.svelte' import TagList from './TagList.svelte' import DedicatedWorkersSelector from './DedicatedWorkersSelector.svelte' + import { computeHashedTag } from './dedicated_worker' function computeVCpuAndMemory(workers: [string, WorkerPing[]][]) { let vcpus = 0 @@ -252,6 +253,21 @@ let openDelete = $state(false) let openClean = $state(false) + // Compute hashed tags for display (actual tags used by the worker) + let hashedDedicatedTags: Map = $state(new Map()) + $effect(() => { + const dws = config?.dedicated_workers ?? (config?.dedicated_worker ? [config.dedicated_worker] : []) + if (dws.length > 0) { + Promise.all(dws.map(async (dw) => [dw, await computeHashedTag(dw)] as const)).then( + (entries) => { + hashedDedicatedTags = new Map(entries) + } + ) + } else { + hashedDedicatedTags = new Map() + } + }) + let drawer: Drawer | undefined = $state() let vcpus_memory = $derived(computeVCpuAndMemory(workers)) let selected = $derived( @@ -1166,21 +1182,27 @@
{:else if config?.dedicated_workers && config.dedicated_workers.length > 0} -
-
Dedicated to:
-
+
+
Dedicated to:
+
{#each config.dedicated_workers as dw} -
- {dw} +
+ {hashedDedicatedTags.get(dw) ?? dw}
{/each}
{:else if config?.dedicated_worker} -
-
Dedicated to:
-
- {config.dedicated_worker} +
+
Dedicated to:
+
+ {hashedDedicatedTags.get(config.dedicated_worker) ?? config.dedicated_worker}
{/if} diff --git a/frontend/src/lib/components/dedicated_worker.ts b/frontend/src/lib/components/dedicated_worker.ts new file mode 100644 index 0000000000..f3b90fde57 --- /dev/null +++ b/frontend/src/lib/components/dedicated_worker.ts @@ -0,0 +1,41 @@ +const MAX_TAG_LEN = 50 +const HASH_SUFFIX_LEN = 16 + +// IMPORTANT: This implementation must stay in sync with the Rust backend +// `dedicated_worker_tag()` in backend/windmill-common/src/worker.rs. +// The frontend version is used for display; the backend version is authoritative. +export async function dedicatedWorkerTag(workspaceId: string, path: string): Promise { + const fullTag = `${workspaceId}:${path}` + if (fullTag.length <= MAX_TAG_LEN) return fullTag + + const encoded = new TextEncoder().encode(fullTag) + const hashBuffer = await crypto.subtle.digest('SHA-256', encoded) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hexHash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') + + const prefixLen = MAX_TAG_LEN - 1 - HASH_SUFFIX_LEN + return `${fullTag.substring(0, prefixLen)}#${hexHash.substring(0, HASH_SUFFIX_LEN)}` +} + +export function parseTag( + tag: string +): { workspace: string; type: 'script' | 'flow'; path: string } | null { + const colonIndex = tag.indexOf(':') + if (colonIndex === -1) return null + + const workspace = tag.substring(0, colonIndex) + const rest = tag.substring(colonIndex + 1) + + if (rest.startsWith('flow/')) { + return { workspace, type: 'flow', path: rest.substring(5) } + } else { + return { workspace, type: 'script', path: rest } + } +} + +export async function computeHashedTag(rawTag: string): Promise { + const parsed = parseTag(rawTag) + if (!parsed) return rawTag + const fullPath = parsed.type === 'flow' ? `flow/${parsed.path}` : parsed.path + return dedicatedWorkerTag(parsed.workspace, fullPath) +} diff --git a/frontend/src/lib/components/flows/content/FlowSettings.svelte b/frontend/src/lib/components/flows/content/FlowSettings.svelte index ad903f40a5..5e0e47da24 100644 --- a/frontend/src/lib/components/flows/content/FlowSettings.svelte +++ b/frontend/src/lib/components/flows/content/FlowSettings.svelte @@ -8,7 +8,7 @@ import { Alert, Button, SecondsInput } from '$lib/components/common' import { getContext } from 'svelte' import type { FlowEditorContext } from '../types' - import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores' + import { enterpriseLicense, userStore } from '$lib/stores' import { isCloudHosted } from '$lib/cloud' import Tooltip from '$lib/components/Tooltip.svelte' import SimpleEditor from '$lib/components/SimpleEditor.svelte' @@ -554,9 +554,8 @@ {#if flowStore.val.dedicated_worker}
- One worker in a worker group needs to be configured with dedicated worker set to:
{$workspaceStore}:flow/{$pathStore}
+ A worker group needs to be configured to listen to this flow. Select it in the dedicated + workers section of the worker group configuration.
{/if} From d95e4db8f31e70a645a5f41e287557933b257db8 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:29:58 +0100 Subject: [PATCH 21/93] feat(ai): add AWS bedrock session token support (#7908) * Add AWS Bedrock session token support in API and worker * Add Bedrock auth mode integration tests for AI agents * Split Bedrock integration test env vars for IAM and session creds * cleaning * Add masked Bedrock bearer-token debug info logs * Revert "Add masked Bedrock bearer-token debug info logs" This reverts commit 6b2fc5e7c2d5b1c6db81f416a4439941a084108c. * cleaning --- backend/windmill-api/src/ai.rs | 21 +++ backend/windmill-api/src/bedrock.rs | 138 +++++++++++++++--- .../src/ai/providers/bedrock.rs | 3 +- backend/windmill-worker/src/ai/types.rs | 12 ++ backend/windmill-worker/src/ai_executor.rs | 1 + integration_tests/ai_agent_tests/.env.example | 14 +- integration_tests/ai_agent_tests/README.md | 14 +- integration_tests/ai_agent_tests/conftest.py | 57 +++++++- integration_tests/ai_agent_tests/providers.py | 43 +++++- .../ai_agent_tests/test_bedrock_auth_modes.py | 111 ++++++++++++++ 10 files changed, 391 insertions(+), 23 deletions(-) create mode 100644 integration_tests/ai_agent_tests/test_bedrock_auth_modes.py diff --git a/backend/windmill-api/src/ai.rs b/backend/windmill-api/src/ai.rs index 94ec94fb60..20c6d7548b 100644 --- a/backend/windmill-api/src/ai.rs +++ b/backend/windmill-api/src/ai.rs @@ -166,6 +166,12 @@ struct AIStandardResource { deserialize_with = "empty_string_as_none" )] aws_secret_access_key: Option, + #[serde( + alias = "awsSessionToken", + default, + deserialize_with = "empty_string_as_none" + )] + aws_session_token: Option, /// Platform for Anthropic API (standard or google_vertex_ai) #[serde(default)] platform: AnthropicPlatform, @@ -199,6 +205,8 @@ struct AIRequestConfig { pub aws_access_key_id: Option, #[allow(dead_code)] pub aws_secret_access_key: Option, + #[allow(dead_code)] + pub aws_session_token: Option, pub platform: AnthropicPlatform, pub enable_1m_context: bool, } @@ -219,6 +227,7 @@ impl AIRequestConfig { region, aws_access_key_id, aws_secret_access_key, + aws_session_token, platform, enable_1m_context, ) = match resource { @@ -253,6 +262,11 @@ impl AIRequestConfig { } else { None }; + let aws_session_token = if let Some(session_token) = resource.aws_session_token { + Some(get_variable_or_self(session_token, db, w_id).await?) + } else { + None + }; ( api_key, @@ -263,6 +277,7 @@ impl AIRequestConfig { region, aws_access_key_id, aws_secret_access_key, + aws_session_token, platform, enable_1m_context, ) @@ -285,6 +300,7 @@ impl AIRequestConfig { None, None, None, + None, AnthropicPlatform::Standard, false, ) @@ -300,6 +316,7 @@ impl AIRequestConfig { region, aws_access_key_id, aws_secret_access_key, + aws_session_token, platform, enable_1m_context, }) @@ -843,6 +860,7 @@ async fn proxy( request_config.api_key.as_deref(), request_config.aws_access_key_id.as_deref(), request_config.aws_secret_access_key.as_deref(), + request_config.aws_session_token.as_deref(), region, ) .await; @@ -851,6 +869,7 @@ async fn proxy( request_config.api_key.as_deref(), request_config.aws_access_key_id.as_deref(), request_config.aws_secret_access_key.as_deref(), + request_config.aws_session_token.as_deref(), region, ) .await; @@ -866,6 +885,7 @@ async fn proxy( request_config.api_key.as_deref(), request_config.aws_access_key_id.as_deref(), request_config.aws_secret_access_key.as_deref(), + request_config.aws_session_token.as_deref(), region, ) .await; @@ -876,6 +896,7 @@ async fn proxy( request_config.api_key.as_deref(), request_config.aws_access_key_id.as_deref(), request_config.aws_secret_access_key.as_deref(), + request_config.aws_session_token.as_deref(), region, ) .await; diff --git a/backend/windmill-api/src/bedrock.rs b/backend/windmill-api/src/bedrock.rs index de0e9059d7..3824f8966d 100644 --- a/backend/windmill-api/src/bedrock.rs +++ b/backend/windmill-api/src/bedrock.rs @@ -71,7 +71,11 @@ struct OpenAIToolFunction { /// Authentication configuration for Bedrock clients enum BedrockAuthConfig { BearerToken(String), - IamCredentials { access_key_id: String, secret_access_key: String }, + IamCredentials { + access_key_id: String, + secret_access_key: String, + session_token: Option, + }, Environment, } @@ -80,6 +84,7 @@ fn determine_auth_config( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, ) -> BedrockAuthConfig { if let Some(key) = api_key.filter(|k| !k.is_empty()) { BedrockAuthConfig::BearerToken(key.to_string()) @@ -90,6 +95,9 @@ fn determine_auth_config( BedrockAuthConfig::IamCredentials { access_key_id: access_key_id.to_string(), secret_access_key: secret_access_key.to_string(), + session_token: aws_session_token + .filter(|token| !token.is_empty()) + .map(str::to_string), } } else { BedrockAuthConfig::Environment @@ -101,12 +109,19 @@ async fn create_bedrock_client( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, region: &str, ) -> Result { - match determine_auth_config(api_key, aws_access_key_id, aws_secret_access_key) { + match determine_auth_config( + api_key, + aws_access_key_id, + aws_secret_access_key, + aws_session_token, + ) { BedrockAuthConfig::BearerToken(key) => BedrockClient::from_bearer_token(key, region).await, - BedrockAuthConfig::IamCredentials { access_key_id, secret_access_key } => { - BedrockClient::from_credentials(access_key_id, secret_access_key, None, region).await + BedrockAuthConfig::IamCredentials { access_key_id, secret_access_key, session_token } => { + BedrockClient::from_credentials(access_key_id, secret_access_key, session_token, region) + .await } BedrockAuthConfig::Environment => BedrockClient::from_env(region).await, } @@ -163,6 +178,7 @@ async fn create_bedrock_control_client( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, region: &str, ) -> Result { use aws_config::BehaviorVersion; @@ -170,7 +186,12 @@ async fn create_bedrock_control_client( let region_provider = aws_sdk_bedrock::config::Region::new(region.to_string()); - match determine_auth_config(api_key, aws_access_key_id, aws_secret_access_key) { + match determine_auth_config( + api_key, + aws_access_key_id, + aws_secret_access_key, + aws_session_token, + ) { BedrockAuthConfig::BearerToken(key) => { let config = aws_sdk_bedrock::config::Builder::new() .region(region_provider) @@ -179,11 +200,11 @@ async fn create_bedrock_control_client( .build(); Ok(aws_sdk_bedrock::Client::from_conf(config)) } - BedrockAuthConfig::IamCredentials { access_key_id, secret_access_key } => { + BedrockAuthConfig::IamCredentials { access_key_id, secret_access_key, session_token } => { let credentials = aws_credential_types::Credentials::new( access_key_id, secret_access_key, - None, + session_token, None, "windmill", ); @@ -209,11 +230,17 @@ pub async fn list_foundation_models( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, region: &str, ) -> Result<(http::StatusCode, http::HeaderMap, axum::body::Body)> { - let client = - create_bedrock_control_client(api_key, aws_access_key_id, aws_secret_access_key, region) - .await?; + let client = create_bedrock_control_client( + api_key, + aws_access_key_id, + aws_secret_access_key, + aws_session_token, + region, + ) + .await?; let response = client .list_foundation_models() @@ -258,11 +285,17 @@ pub async fn list_inference_profiles( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, region: &str, ) -> Result<(http::StatusCode, http::HeaderMap, axum::body::Body)> { - let client = - create_bedrock_control_client(api_key, aws_access_key_id, aws_secret_access_key, region) - .await?; + let client = create_bedrock_control_client( + api_key, + aws_access_key_id, + aws_secret_access_key, + aws_session_token, + region, + ) + .await?; let response = client.list_inference_profiles().send().await.map_err(|e| { @@ -315,14 +348,21 @@ pub async fn handle_bedrock_sdk_streaming( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, region: &str, ) -> Result<(http::StatusCode, http::HeaderMap, axum::body::Body)> { let openai_req: OpenAIRequest = serde_json::from_slice(body) .map_err(|e| Error::internal_err(format!("Failed to parse OpenAI request: {}", e)))?; // Create Bedrock client using shared helper - let bedrock_client = - create_bedrock_client(api_key, aws_access_key_id, aws_secret_access_key, region).await?; + let bedrock_client = create_bedrock_client( + api_key, + aws_access_key_id, + aws_secret_access_key, + aws_session_token, + region, + ) + .await?; // Convert messages using shared conversion let (bedrock_messages, system_prompts) = @@ -568,14 +608,21 @@ pub async fn handle_bedrock_sdk_non_streaming( api_key: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, region: &str, ) -> Result<(http::StatusCode, http::HeaderMap, axum::body::Body)> { let openai_req: OpenAIRequest = serde_json::from_slice(body) .map_err(|e| Error::internal_err(format!("Failed to parse OpenAI request: {}", e)))?; // Create Bedrock client using shared helper - let bedrock_client = - create_bedrock_client(api_key, aws_access_key_id, aws_secret_access_key, region).await?; + let bedrock_client = create_bedrock_client( + api_key, + aws_access_key_id, + aws_secret_access_key, + aws_session_token, + region, + ) + .await?; // Convert messages using shared conversion let (bedrock_messages, system_prompts) = @@ -754,3 +801,60 @@ fn document_to_json(doc: &aws_smithy_types::Document) -> serde_json::Value { aws_smithy_types::Document::Null => serde_json::Value::Null, } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn determine_auth_config_prioritizes_bearer_token() { + let config = determine_auth_config( + Some("bearer-token"), + Some("AKIA123"), + Some("secret"), + Some("session-token"), + ); + + match config { + BedrockAuthConfig::BearerToken(token) => assert_eq!(token, "bearer-token"), + _ => panic!("expected bearer token auth config"), + } + } + + #[test] + fn determine_auth_config_uses_iam_with_optional_session_token() { + let config = + determine_auth_config(None, Some("AKIA123"), Some("secret"), Some("session-token")); + + match config { + BedrockAuthConfig::IamCredentials { + access_key_id, + secret_access_key, + session_token, + } => { + assert_eq!(access_key_id, "AKIA123"); + assert_eq!(secret_access_key, "secret"); + assert_eq!(session_token.as_deref(), Some("session-token")); + } + _ => panic!("expected IAM auth config"), + } + } + + #[test] + fn determine_auth_config_treats_empty_session_token_as_none() { + let config = determine_auth_config(None, Some("AKIA123"), Some("secret"), Some("")); + + match config { + BedrockAuthConfig::IamCredentials { session_token, .. } => { + assert!(session_token.is_none()); + } + _ => panic!("expected IAM auth config"), + } + } + + #[test] + fn determine_auth_config_falls_back_to_environment() { + let config = determine_auth_config(None, Some("AKIA123"), None, Some("session-token")); + assert!(matches!(config, BedrockAuthConfig::Environment)); + } +} diff --git a/backend/windmill-worker/src/ai/providers/bedrock.rs b/backend/windmill-worker/src/ai/providers/bedrock.rs index a86bf3250c..18f44f0573 100644 --- a/backend/windmill-worker/src/ai/providers/bedrock.rs +++ b/backend/windmill-worker/src/ai/providers/bedrock.rs @@ -49,6 +49,7 @@ impl BedrockQueryBuilder { structured_output_tool_name: Option<&str>, aws_access_key_id: Option<&str>, aws_secret_access_key: Option<&str>, + aws_session_token: Option<&str>, ) -> Result { let bedrock_client = if !api_key.is_empty() { BedrockClient::from_bearer_token(api_key.to_string(), region).await? @@ -58,7 +59,7 @@ impl BedrockQueryBuilder { BedrockClient::from_credentials( access_key_id.to_string(), secret_access_key.to_string(), - None, + aws_session_token.map(str::to_string), region, ) .await? diff --git a/backend/windmill-worker/src/ai/types.rs b/backend/windmill-worker/src/ai/types.rs index 0283c52a2f..bd59ee79cc 100644 --- a/backend/windmill-worker/src/ai/types.rs +++ b/backend/windmill-worker/src/ai/types.rs @@ -187,6 +187,13 @@ pub struct ProviderResource { deserialize_with = "empty_string_as_none" )] pub aws_secret_access_key: Option, + #[allow(dead_code)] + #[serde( + alias = "awsSessionToken", + default, + deserialize_with = "empty_string_as_none" + )] + pub aws_session_token: Option, /// Platform for Anthropic API (standard or google_vertex_ai) #[serde(default)] pub platform: AnthropicPlatform, @@ -232,6 +239,11 @@ impl ProviderWithResource { self.resource.aws_secret_access_key.as_deref() } + #[cfg(feature = "bedrock")] + pub fn get_aws_session_token(&self) -> Option<&str> { + self.resource.aws_session_token.as_deref() + } + pub fn get_platform(&self) -> &AnthropicPlatform { &self.resource.platform } diff --git a/backend/windmill-worker/src/ai_executor.rs b/backend/windmill-worker/src/ai_executor.rs index 0b820e3816..3a721b4a5e 100644 --- a/backend/windmill-worker/src/ai_executor.rs +++ b/backend/windmill-worker/src/ai_executor.rs @@ -686,6 +686,7 @@ pub async fn run_agent( structured_output_tool_name.as_deref(), args.provider.get_aws_access_key_id(), args.provider.get_aws_secret_access_key(), + args.provider.get_aws_session_token(), ) .await? } diff --git a/integration_tests/ai_agent_tests/.env.example b/integration_tests/ai_agent_tests/.env.example index 93904d1ccc..7750354341 100644 --- a/integration_tests/ai_agent_tests/.env.example +++ b/integration_tests/ai_agent_tests/.env.example @@ -2,4 +2,16 @@ OPENAI_API_KEY= ANTHROPIC_API_KEY= GOOGLE_AI_API_KEY= OPENROUTER_API_KEY= -BEDROCK_API_KEY= \ No newline at end of file +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_BASE_URL= + +BEDROCK_API_KEY= + +BEDROCK_IAM_ACCESS_KEY_ID= +BEDROCK_IAM_SECRET_ACCESS_KEY= + +BEDROCK_SESSION_ACCESS_KEY_ID= +BEDROCK_SESSION_SECRET_ACCESS_KEY= +BEDROCK_SESSION_TOKEN= \ No newline at end of file diff --git a/integration_tests/ai_agent_tests/README.md b/integration_tests/ai_agent_tests/README.md index e4d1f4ecb1..91470d1ad3 100644 --- a/integration_tests/ai_agent_tests/README.md +++ b/integration_tests/ai_agent_tests/README.md @@ -23,6 +23,15 @@ Integration tests for Windmill AI agents using the `preview_flow` endpoint. GOOGLE_AI_API_KEY=... OPENROUTER_API_KEY=sk-or-... BEDROCK_API_KEY=... + BEDROCK_IAM_ACCESS_KEY_ID=... + BEDROCK_IAM_SECRET_ACCESS_KEY=... + BEDROCK_SESSION_ACCESS_KEY_ID=... + BEDROCK_SESSION_SECRET_ACCESS_KEY=... + BEDROCK_SESSION_TOKEN=... # optional, only for session-token Bedrock test + AWS_ACCESS_KEY_ID=... # optional, for env-fallback Bedrock test + AWS_SECRET_ACCESS_KEY=... # optional, for env-fallback Bedrock test + AWS_SESSION_TOKEN=... # optional, required for env-fallback if AWS_ACCESS_KEY_ID is ASIA... + BEDROCK_REGION=us-east-1 # optional ``` 4. Run a single test to verify: @@ -48,7 +57,10 @@ The `setup_providers` fixture automatically creates Windmill variables and resou | Anthropic | `u/admin/anthropic_api_key` | `u/admin/anthropic` | anthropic | | Google AI | `u/admin/google_ai_api_key` | `u/admin/googleai` | googleai | | OpenRouter | `u/admin/openrouter_api_key` | `u/admin/openrouter` | openrouter | -| Bedrock | `u/admin/bedrock_api_key` | `u/admin/bedrock` | aws_bedrock | +| Bedrock (api key) | `u/admin/bedrock_api_key` | `u/admin/bedrock` | aws_bedrock | +| Bedrock (IAM) | `u/admin/bedrock_iam_access_key_id`, `u/admin/bedrock_iam_secret_access_key` | `u/admin/bedrock_iam` | aws_bedrock | +| Bedrock (IAM + session) | `u/admin/bedrock_session_access_key_id`, `u/admin/bedrock_session_secret_access_key`, `u/admin/bedrock_session_token` | `u/admin/bedrock_iam_session` | aws_bedrock | +| Bedrock (environment fallback) | none (uses worker/api AWS env) | `u/admin/bedrock_env` | aws_bedrock | ### Tools diff --git a/integration_tests/ai_agent_tests/conftest.py b/integration_tests/ai_agent_tests/conftest.py index 921ebcb73f..681304e614 100644 --- a/integration_tests/ai_agent_tests/conftest.py +++ b/integration_tests/ai_agent_tests/conftest.py @@ -438,6 +438,12 @@ def setup_providers(client): - GOOGLE_AI_API_KEY - OPENROUTER_API_KEY - BEDROCK_API_KEY (optional) + - BEDROCK_IAM_ACCESS_KEY_ID and BEDROCK_IAM_SECRET_ACCESS_KEY (optional, for IAM Bedrock tests) + - BEDROCK_SESSION_ACCESS_KEY_ID, BEDROCK_SESSION_SECRET_ACCESS_KEY, BEDROCK_SESSION_TOKEN + (optional, for IAM session Bedrock tests) + - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (optional, for environment fallback tests) + - AWS_SESSION_TOKEN (optional, if environment fallback uses temporary credentials) + - BEDROCK_REGION (optional, defaults to us-east-1) """ # OpenAI if os.environ.get("OPENAI_API_KEY"): @@ -475,14 +481,63 @@ def setup_providers(client): "api_key": "$var:u/admin/openrouter_api_key" }) + bedrock_region = os.environ.get("BEDROCK_REGION", "us-east-1") + # Bedrock (using apiKey approach) if os.environ.get("BEDROCK_API_KEY"): client.create_variable("u/admin/bedrock_api_key", os.environ["BEDROCK_API_KEY"]) client.create_resource("u/admin/bedrock", "aws_bedrock", { "apiKey": "$var:u/admin/bedrock_api_key", - "region": "us-east-1" + "region": bedrock_region }) + # Bedrock IAM credentials without session token + if os.environ.get("BEDROCK_IAM_ACCESS_KEY_ID") and os.environ.get("BEDROCK_IAM_SECRET_ACCESS_KEY"): + client.create_variable( + "u/admin/bedrock_iam_access_key_id", + os.environ["BEDROCK_IAM_ACCESS_KEY_ID"], + ) + client.create_variable( + "u/admin/bedrock_iam_secret_access_key", + os.environ["BEDROCK_IAM_SECRET_ACCESS_KEY"], + ) + client.create_resource("u/admin/bedrock_iam", "aws_bedrock", { + "awsAccessKeyId": "$var:u/admin/bedrock_iam_access_key_id", + "awsSecretAccessKey": "$var:u/admin/bedrock_iam_secret_access_key", + "region": bedrock_region + }) + + # Bedrock IAM credentials with session token + if ( + os.environ.get("BEDROCK_SESSION_ACCESS_KEY_ID") + and os.environ.get("BEDROCK_SESSION_SECRET_ACCESS_KEY") + and os.environ.get("BEDROCK_SESSION_TOKEN") + ): + client.create_variable( + "u/admin/bedrock_session_access_key_id", + os.environ["BEDROCK_SESSION_ACCESS_KEY_ID"], + ) + client.create_variable( + "u/admin/bedrock_session_secret_access_key", + os.environ["BEDROCK_SESSION_SECRET_ACCESS_KEY"], + ) + client.create_variable( + "u/admin/bedrock_session_token", + os.environ["BEDROCK_SESSION_TOKEN"], + ) + client.create_resource("u/admin/bedrock_iam_session", "aws_bedrock", { + "awsAccessKeyId": "$var:u/admin/bedrock_session_access_key_id", + "awsSecretAccessKey": "$var:u/admin/bedrock_session_secret_access_key", + "awsSessionToken": "$var:u/admin/bedrock_session_token", + "region": bedrock_region + }) + + # Bedrock using environment credentials fallback + # This resource intentionally omits explicit credentials. + client.create_resource("u/admin/bedrock_env", "aws_bedrock", { + "region": bedrock_region + }) + # DeepWiki MCP resource (always created for MCP tool tests) client.create_resource("u/admin/deepwiki", "mcp", { "url": "https://mcp.deepwiki.com/mcp", diff --git a/integration_tests/ai_agent_tests/providers.py b/integration_tests/ai_agent_tests/providers.py index 2afd1f3d91..afacda5ed1 100644 --- a/integration_tests/ai_agent_tests/providers.py +++ b/integration_tests/ai_agent_tests/providers.py @@ -9,7 +9,10 @@ Resources created: - u/admin/anthropic (anthropic type) - u/admin/googleai (googleai type) - u/admin/openrouter (openrouter type) -- u/admin/bedrock (aws_bedrock type) +- u/admin/bedrock (aws_bedrock type, api key auth) +- u/admin/bedrock_iam (aws_bedrock type, IAM keys auth) +- u/admin/bedrock_env (aws_bedrock type, environment auth) +- u/admin/bedrock_iam_session (aws_bedrock type, IAM session auth) """ from typing import Any @@ -78,6 +81,42 @@ BEDROCK = { ), } +BEDROCK_API_KEY = { + "name": "bedrock_api_key", + "input_transform": make_provider_input_transform( + kind="aws_bedrock", + model="global.anthropic.claude-haiku-4-5-20251001-v1:0", + resource_path="u/admin/bedrock", + ), +} + +BEDROCK_IAM = { + "name": "bedrock_iam", + "input_transform": make_provider_input_transform( + kind="aws_bedrock", + model="global.anthropic.claude-haiku-4-5-20251001-v1:0", + resource_path="u/admin/bedrock_iam", + ), +} + +BEDROCK_ENV = { + "name": "bedrock_env", + "input_transform": make_provider_input_transform( + kind="aws_bedrock", + model="global.anthropic.claude-haiku-4-5-20251001-v1:0", + resource_path="u/admin/bedrock_env", + ), +} + +BEDROCK_IAM_SESSION = { + "name": "bedrock_iam_session", + "input_transform": make_provider_input_transform( + kind="aws_bedrock", + model="global.anthropic.claude-haiku-4-5-20251001-v1:0", + resource_path="u/admin/bedrock_iam_session", + ), +} + OPENROUTER = { "name": "openrouter", "input_transform": make_provider_input_transform( @@ -107,4 +146,4 @@ VISION_PROVIDERS = [ def get_provider_ids(providers: list[dict[str, Any]]) -> list[str]: """Get provider names for pytest parametrize IDs.""" - return [p["name"] for p in providers] \ No newline at end of file + return [p["name"] for p in providers] diff --git a/integration_tests/ai_agent_tests/test_bedrock_auth_modes.py b/integration_tests/ai_agent_tests/test_bedrock_auth_modes.py new file mode 100644 index 0000000000..c45bf95ae3 --- /dev/null +++ b/integration_tests/ai_agent_tests/test_bedrock_auth_modes.py @@ -0,0 +1,111 @@ +""" +Bedrock auth mode tests for AI agents. + +Verifies Bedrock works with the following resource auth modes: +- API key +- IAM access key + secret key (no session token) +- Environment credential fallback +- IAM access key + secret key + session token +""" + +import os + +import pytest + +from .conftest import AIAgentTestClient, create_ai_agent_flow +from .providers import BEDROCK_API_KEY, BEDROCK_ENV, BEDROCK_IAM, BEDROCK_IAM_SESSION + + +class TestBedrockAuthModes: + """Test AI agent Bedrock auth modes.""" + + @pytest.mark.usefixtures("setup_providers") + def test_bedrock_api_key_resource(self, client: AIAgentTestClient): + """Test Bedrock with API key resource auth.""" + if not os.environ.get("BEDROCK_API_KEY"): + pytest.skip("BEDROCK_API_KEY not set") + + flow_value = create_ai_agent_flow( + provider_input_transform=BEDROCK_API_KEY["input_transform"], + system_prompt="You are a concise assistant.", + ) + result = client.run_preview_flow( + flow_value=flow_value, + args={"user_message": "Reply with one short sentence."}, + ) + + assert isinstance(result.get("output"), str), f"Expected string output, got: {result}" + assert result.get("output", "").strip(), f"Expected non-empty output, got: {result}" + + @pytest.mark.usefixtures("setup_providers") + def test_bedrock_iam_resource_without_session_token(self, client: AIAgentTestClient): + """Test Bedrock with IAM access key + secret key auth.""" + if not ( + os.environ.get("BEDROCK_IAM_ACCESS_KEY_ID") + and os.environ.get("BEDROCK_IAM_SECRET_ACCESS_KEY") + ): + pytest.skip( + "BEDROCK_IAM_ACCESS_KEY_ID and BEDROCK_IAM_SECRET_ACCESS_KEY required" + ) + + flow_value = create_ai_agent_flow( + provider_input_transform=BEDROCK_IAM["input_transform"], + system_prompt="You are a concise assistant.", + ) + result = client.run_preview_flow( + flow_value=flow_value, + args={"user_message": "Reply with one short sentence."}, + ) + + assert isinstance(result.get("output"), str), f"Expected string output, got: {result}" + assert result.get("output", "").strip(), f"Expected non-empty output, got: {result}" + + @pytest.mark.usefixtures("setup_providers") + def test_bedrock_environment_resource(self, client: AIAgentTestClient): + """Test Bedrock with environment credential fallback.""" + aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID") + aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY") + aws_session_token = os.environ.get("AWS_SESSION_TOKEN") + + if not (aws_access_key_id and aws_secret_access_key): + pytest.skip("AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY required for env fallback") + if aws_access_key_id.startswith("ASIA") and not aws_session_token: + pytest.skip( + "AWS_SESSION_TOKEN required for env fallback when AWS_ACCESS_KEY_ID is temporary (ASIA...)" + ) + + flow_value = create_ai_agent_flow( + provider_input_transform=BEDROCK_ENV["input_transform"], + system_prompt="You are a concise assistant.", + ) + result = client.run_preview_flow( + flow_value=flow_value, + args={"user_message": "Reply with one short sentence."}, + ) + + assert isinstance(result.get("output"), str), f"Expected string output, got: {result}" + assert result.get("output", "").strip(), f"Expected non-empty output, got: {result}" + + @pytest.mark.usefixtures("setup_providers") + def test_bedrock_iam_resource_with_session_token(self, client: AIAgentTestClient): + """Test Bedrock with IAM access key + secret key + session token auth.""" + if not ( + os.environ.get("BEDROCK_SESSION_ACCESS_KEY_ID") + and os.environ.get("BEDROCK_SESSION_SECRET_ACCESS_KEY") + and os.environ.get("BEDROCK_SESSION_TOKEN") + ): + pytest.skip( + "BEDROCK_SESSION_ACCESS_KEY_ID, BEDROCK_SESSION_SECRET_ACCESS_KEY, and BEDROCK_SESSION_TOKEN required" + ) + + flow_value = create_ai_agent_flow( + provider_input_transform=BEDROCK_IAM_SESSION["input_transform"], + system_prompt="You are a concise assistant.", + ) + result = client.run_preview_flow( + flow_value=flow_value, + args={"user_message": "Reply with one short sentence."}, + ) + + assert isinstance(result.get("output"), str), f"Expected string output, got: {result}" + assert result.get("output", "").strip(), f"Expected non-empty output, got: {result}" From 7e3f81605a6efa94c4b22b9f7ffc4d212f89cf48 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 11 Feb 2026 23:15:19 +0100 Subject: [PATCH 22/93] internal: git action cleaning (#7920) * cleaning * cleaning --- .github/workflows/check-org-membership.yml | 23 +++++++++-- .github/workflows/claude-fast.yml | 26 +------------ .github/workflows/claude-plan.yml | 26 +------------ .github/workflows/claude.yml | 26 +------------ .github/workflows/create-docs.yml | 39 ------------------- .github/workflows/git-commands.yaml | 24 ++++++++++-- .github/workflows/spawn-ephemeral-backend.yml | 26 +------------ 7 files changed, 48 insertions(+), 142 deletions(-) delete mode 100644 .github/workflows/create-docs.yml diff --git a/.github/workflows/check-org-membership.yml b/.github/workflows/check-org-membership.yml index d8d648e659..eb338d3188 100644 --- a/.github/workflows/check-org-membership.yml +++ b/.github/workflows/check-org-membership.yml @@ -4,9 +4,10 @@ on: workflow_call: inputs: commenter: - required: true + required: false type: string - description: 'The username to check for organization membership' + default: '' + description: 'The username to check. Auto-detected from the event context if not provided.' organization: required: false type: string @@ -32,11 +33,27 @@ jobs: outputs: is_member: ${{ steps.check-membership.outputs.is_member }} steps: + - name: Determine commenter + id: determine-commenter + run: | + COMMENTER="${{ inputs.commenter }}" + if [[ -z "$COMMENTER" ]]; then + if [[ "${{ github.event_name }}" == "issue_comment" || \ + "${{ github.event_name }}" == "pull_request_review_comment" ]]; then + COMMENTER="${{ github.event.comment.user.login }}" + elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then + COMMENTER="${{ github.event.review.user.login }}" + else + COMMENTER="${{ github.event.issue.user.login }}" + fi + fi + echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT + - name: Check organization membership id: check-membership env: ORG_ACCESS_TOKEN: ${{ secrets.access_token }} - COMMENTER: ${{ inputs.commenter }} + COMMENTER: ${{ steps.determine-commenter.outputs.commenter }} ORG: ${{ inputs.organization }} TRUSTED_BOT: ${{ inputs.trusted_bot }} run: | diff --git a/.github/workflows/claude-fast.yml b/.github/workflows/claude-fast.yml index 9ca2f5d8ce..4ad53d326f 100644 --- a/.github/workflows/claude-fast.yml +++ b/.github/workflows/claude-fast.yml @@ -11,40 +11,18 @@ on: types: [submitted] jobs: - determine-commenter: + check-membership: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai-fast')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai-fast')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai-fast')) || (github.event_name == 'issues' && contains(github.event.issue.body, '/ai-fast')) - runs-on: ubicloud-standard-2 - outputs: - commenter: ${{ steps.determine-commenter.outputs.commenter }} - steps: - - name: Determine commenter - id: determine-commenter - run: | - # Work out who wrote the comment / review - if [[ "${{ github.event_name }}" == "issue_comment" || \ - "${{ github.event_name }}" == "pull_request_review_comment" ]]; then - COMMENTER="${{ github.event.comment.user.login }}" - elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then - COMMENTER="${{ github.event.review.user.login }}" - else - COMMENTER="${{ github.event.issue.user.login }}" - fi - echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT - - check-membership: - needs: determine-commenter uses: ./.github/workflows/check-org-membership.yml - with: - commenter: ${{ needs.determine-commenter.outputs.commenter }} secrets: access_token: ${{ secrets.ORG_ACCESS_TOKEN }} claude-code-action: - needs: [determine-commenter, check-membership] + needs: check-membership if: | needs.check-membership.outputs.is_member == 'true' runs-on: ubicloud-standard-8 diff --git a/.github/workflows/claude-plan.yml b/.github/workflows/claude-plan.yml index c9abc3465b..c0457dabed 100644 --- a/.github/workflows/claude-plan.yml +++ b/.github/workflows/claude-plan.yml @@ -11,40 +11,18 @@ on: types: [submitted] jobs: - determine-commenter: + check-membership: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/plan')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/plan')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/plan')) || (github.event_name == 'issues' && contains(github.event.issue.body, '/plan')) - runs-on: ubicloud-standard-2 - outputs: - commenter: ${{ steps.determine-commenter.outputs.commenter }} - steps: - - name: Determine commenter - id: determine-commenter - run: | - # Work out who wrote the comment / review - if [[ "${{ github.event_name }}" == "issue_comment" || \ - "${{ github.event_name }}" == "pull_request_review_comment" ]]; then - COMMENTER="${{ github.event.comment.user.login }}" - elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then - COMMENTER="${{ github.event.review.user.login }}" - else - COMMENTER="${{ github.event.issue.user.login }}" - fi - echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT - - check-membership: - needs: determine-commenter uses: ./.github/workflows/check-org-membership.yml - with: - commenter: ${{ needs.determine-commenter.outputs.commenter }} secrets: access_token: ${{ secrets.ORG_ACCESS_TOKEN }} claude-plan-action: - needs: [determine-commenter, check-membership] + needs: check-membership if: | needs.check-membership.outputs.is_member == 'true' runs-on: ubicloud-standard-4 diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index ad1e234d98..88dacf80ef 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -11,40 +11,18 @@ on: types: [submitted] jobs: - determine-commenter: + check-membership: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai')) || (github.event_name == 'issues' && contains(github.event.issue.body, '/ai')) - runs-on: ubicloud-standard-2 - outputs: - commenter: ${{ steps.determine-commenter.outputs.commenter }} - steps: - - name: Determine commenter - id: determine-commenter - run: | - # Work out who wrote the comment / review - if [[ "${{ github.event_name }}" == "issue_comment" || \ - "${{ github.event_name }}" == "pull_request_review_comment" ]]; then - COMMENTER="${{ github.event.comment.user.login }}" - elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then - COMMENTER="${{ github.event.review.user.login }}" - else - COMMENTER="${{ github.event.issue.user.login }}" - fi - echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT - - check-membership: - needs: determine-commenter uses: ./.github/workflows/check-org-membership.yml - with: - commenter: ${{ needs.determine-commenter.outputs.commenter }} secrets: access_token: ${{ secrets.ORG_ACCESS_TOKEN }} claude-code-action: - needs: [determine-commenter, check-membership] + needs: check-membership if: | needs.check-membership.outputs.is_member == 'true' runs-on: ubicloud-standard-8 diff --git a/.github/workflows/create-docs.yml b/.github/workflows/create-docs.yml deleted file mode 100644 index e6b313617c..0000000000 --- a/.github/workflows/create-docs.yml +++ /dev/null @@ -1,39 +0,0 @@ -on: - issue_comment: - types: [created] - -jobs: - check-membership: - if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') }} - uses: ./.github/workflows/check-org-membership.yml - with: - commenter: ${{ github.event.comment.user.login }} - secrets: - access_token: ${{ secrets.ORG_ACCESS_TOKEN }} - - generate-token: - needs: check-membership - if: ${{ needs.check-membership.outputs.is_member == 'true' }} - runs-on: ubicloud-standard-2 - outputs: - app_token: ${{ steps.app.outputs.token }} - steps: - - name: Generate an installation token - id: app - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.INTERNAL_APP_ID }} - private-key: ${{ secrets.INTERNAL_APP_KEY }} - owner: windmill-labs - - trigger-docs: - needs: [generate-token, check-membership] - if: ${{ needs.check-membership.outputs.is_member == 'true' }} - uses: windmill-labs/windmilldocs/.github/workflows/create-docs.yml@main - with: - pr_number: ${{ github.event.issue.number }} - repo: ${{ github.event.repository.name }} - comment_text: ${{ github.event.comment.body }} - secrets: - DOCS_TOKEN: ${{ needs.generate-token.outputs.app_token }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} diff --git a/.github/workflows/git-commands.yaml b/.github/workflows/git-commands.yaml index b9602d122e..4e93cd8e9b 100644 --- a/.github/workflows/git-commands.yaml +++ b/.github/workflows/git-commands.yaml @@ -5,8 +5,21 @@ on: types: [created] jobs: + check-membership: + if: >- + github.event.issue.pull_request && ( + startsWith(github.event.comment.body, '/updatesqlx') || + startsWith(github.event.comment.body, '/demo') || + startsWith(github.event.comment.body, '/eeref') || + startsWith(github.event.comment.body, '/docs') + ) + uses: ./.github/workflows/check-org-membership.yml + secrets: + access_token: ${{ secrets.ORG_ACCESS_TOKEN }} + update-sqlx: - if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/updatesqlx') + needs: check-membership + if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/updatesqlx') runs-on: ubicloud-standard-8 permissions: contents: write @@ -121,7 +134,8 @@ jobs: }) demo: - if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/demo') + needs: check-membership + if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/demo') runs-on: ubicloud-standard-2 permissions: contents: read @@ -200,7 +214,8 @@ jobs: fi update-ee-ref: - if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/eeref') + needs: check-membership + if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/eeref') runs-on: ubicloud-standard-2 permissions: contents: write @@ -285,7 +300,8 @@ jobs: }) update-docs: - if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') + needs: check-membership + if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/docs') runs-on: ubicloud-standard-2 permissions: contents: read diff --git a/.github/workflows/spawn-ephemeral-backend.yml b/.github/workflows/spawn-ephemeral-backend.yml index 9070f5af0d..725890031a 100644 --- a/.github/workflows/spawn-ephemeral-backend.yml +++ b/.github/workflows/spawn-ephemeral-backend.yml @@ -13,38 +13,16 @@ on: type: number jobs: - determine-commenter: + check-membership: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/spawnbackend')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/spawnbackend')) - runs-on: ubicloud-standard-2 - outputs: - commenter: ${{ steps.determine-commenter.outputs.commenter }} - steps: - - name: Determine commenter - id: determine-commenter - run: | - # Work out who wrote the comment / review - if [[ "${{ github.event_name }}" == "issue_comment" || \ - "${{ github.event_name }}" == "pull_request_review_comment" ]]; then - COMMENTER="${{ github.event.comment.user.login }}" - elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then - COMMENTER="${{ github.event.review.user.login }}" - else - COMMENTER="${{ github.event.issue.user.login }}" - fi - echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT - - check-membership: - needs: determine-commenter uses: ./.github/workflows/check-org-membership.yml - with: - commenter: ${{ needs.determine-commenter.outputs.commenter }} secrets: access_token: ${{ secrets.ORG_ACCESS_TOKEN }} spawn-backend: - needs: [determine-commenter, check-membership] + needs: check-membership # Only run on PR comments that contain /spawn-backend, or manual dispatch if: | github.event_name == 'workflow_dispatch' || From 2dad2b43a40ed998cea8326c28183a08156d6209 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:19:44 +0100 Subject: [PATCH 23/93] internal: Improve instructions for claude (#7921) * Improve CLAUDE.md instructions and compact DB schema summary - Add code validation instructions (cargo check, npm run check) to all CLAUDE.md files with guidance to use only relevant feature flags - Reference backend/CLAUDE.md and frontend/CLAUDE.md paths from root - Add database querying guidance (psql commands for exact table info) - Compact summarize_schema.py output: inline columns, shorten types, one-line enums, drop indexes (use psql \d for exact info) - Fix FK parsing for multi-line ALTER TABLE statements - Result: schema summary reduced from 1514 lines/40KB to 194 lines/23KB * cleaning * fix: use prefix-based type abbreviations and filter CONSTRAINT pseudo-columns - Change TYPE_ABBREVIATIONS matching from exact to prefix-based so parametrized types (character(64) -> char(64)) and array types (integer[] -> int[], real[] -> float[]) are properly abbreviated - Skip CONSTRAINT lines inside CREATE TABLE blocks that were being incorrectly matched as columns by the column regex - Update summarized_schema.txt to reflect both changes Co-authored-by: centdix --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: centdix --- .../skills/svelte-frontend/SKILL.md | 6 +- CLAUDE.md | 21 +- backend/CLAUDE.md | 18 + backend/summarize_schema.py | 109 +- backend/summarized_schema.txt | 1278 +++-------------- frontend/CLAUDE.md | 10 +- 6 files changed, 313 insertions(+), 1129 deletions(-) rename frontend/svelte5-best-practices.mdc => .claude/skills/svelte-frontend/SKILL.md (98%) diff --git a/frontend/svelte5-best-practices.mdc b/.claude/skills/svelte-frontend/SKILL.md similarity index 98% rename from frontend/svelte5-best-practices.mdc rename to .claude/skills/svelte-frontend/SKILL.md index 6951584bc5..41d65a12be 100644 --- a/frontend/svelte5-best-practices.mdc +++ b/.claude/skills/svelte-frontend/SKILL.md @@ -1,8 +1,8 @@ --- -description: -globs: frontend/src/**/*.svelte -alwaysApply: false +name: svelte-frontend +description: Svelte coding guidelines for the Windmill frontend. MUST use when writing or modifying code in the frontend directory. --- + # Svelte 5 Best Practices This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. These rules MUST NOT be applied on svelte 4 files unless explicitly asked to do so. diff --git a/CLAUDE.md b/CLAUDE.md index 9f68fae3a1..bea5fc855e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,19 +17,32 @@ When implementing new features in Windmill, follow these best practices: ## Language-Specific Guides -- Backend (Rust): @backend/rust-best-practices.mdc + @backend/summarized_schema.txt -- Frontend (Svelte 5): @frontend/svelte5-best-practices.mdc +- Backend (Rust): see `backend/CLAUDE.md` and the `rust-backend` skill: `.claude/skills/rust-backend/SKILL.md` +- Frontend (Svelte 5): see `frontend/CLAUDE.md` and the `svelte-frontend` skill: `.claude/skills/svelte-frontend/SKILL.md` + +## Code Validation (MUST DO) + +After making code changes, you MUST run the appropriate checks and fix all errors before considering the work done: + +- **Backend**: Run `cargo check` from the `backend/` directory. Only enable the feature flags needed for the code you changed — check `backend/Cargo.toml` `[features]` section to identify which flags gate the crates/modules you modified. For example: `cargo check --features enterprise,parquet` if you only touched enterprise and parquet code. +- **Frontend**: Run `npm run check` from the `frontend/` directory. ## Querying the Database -To query the database directly, use psql with the following connection string: +`backend/summarized_schema.txt` provides a compact overview of all tables, columns, types, ENUMs, and foreign keys. Use it to quickly understand the data model and relationships. Note: this file is a simplified summary — it omits indexes, constraints details, and other metadata. + +For exact table definitions (indexes, constraints, column defaults, etc.), query the database directly: ```bash psql postgres://postgres:changeme@localhost:5432/windmill ``` -This can be helpful for: +Useful psql commands: +- `\d ` — full table definition with indexes and constraints +- `\di *` — list indexes for a table +- `\d+ ` — extended table info including storage and descriptions +This is also helpful for: - Inspecting database state during development - Testing queries before implementing them in Rust - Debugging data-related issues diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index edfeb5207d..54550c2dcb 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -49,6 +49,24 @@ Windmill uses a workspace-based architecture with multiple crates: - Use feature flags: `#[cfg(feature = "enterprise")]` - Isolate enterprise code in separate modules +## Code Validation (MUST DO) + +After making backend changes, you MUST run `cargo check` and fix all errors and warnings before considering the work done. + +Only enable the feature flags relevant to your changes — do NOT use `all_sqlx_features` as it compiles the entire codebase and is very slow. Check the `[features]` section in `Cargo.toml` to identify which flags gate the crates/modules you modified. + +Examples: +```bash +# Changed core code (no feature-gated modules) +cargo check + +# Changed code behind the enterprise feature +cargo check --features enterprise + +# Changed kafka trigger code +cargo check --features kafka +``` + ## Git Workflow - **Never push directly to main** — always create a branch and open a pull request diff --git a/backend/summarize_schema.py b/backend/summarize_schema.py index a6d929ba9a..daa7f5e93c 100644 --- a/backend/summarize_schema.py +++ b/backend/summarize_schema.py @@ -1,6 +1,6 @@ # This script is used to summarize the database schema. # You can use pg_dump to dump the schema to a file. -# pg_dump --file "schema.sql" --host "localhost" --port "5432" --username "postgres" --no-password --format=c --large-objects --schema-only --no-owner --no-privileges --no-tablespaces --no-unlogged-table-data --no-comments --no-publications --no-subscriptions --no-security-labels --no-toast-compression --no-table-access-method --verbose --schema "public" "windmill" +# pg_dump --file "schema.sql" --format=p --large-objects --schema-only --no-owner --no-privileges --no-tablespaces --no-unlogged-table-data --no-comments --no-publications --no-subscriptions --no-security-labels --no-toast-compression --no-table-access-method --verbose --schema "public" "postgresql://postgres:changeme@localhost:5432/windmill" # Then you can run python summarize_schema.py schema.sql to get the summarized schema. import re @@ -17,6 +17,7 @@ def summarize_schema(file_path): # Use state variables to parse multi-line definitions current_table = None current_enum = None + pending_alter_table = None # For multi-line ALTER TABLE ... ADD CONSTRAINT with open(file_path, 'r', encoding='utf-8') as f: for line in f: @@ -54,6 +55,9 @@ def summarize_schema(file_path): match_column = re.match(r'^"?(\w+)"?\s+([\w\d\.\[\]\(\)]+)', line) if match_column: col_name = match_column.group(1) + # Skip CONSTRAINT definitions (e.g., CHECK, UNIQUE) mistakenly matched as columns + if col_name.upper() == 'CONSTRAINT': + continue col_type = match_column.group(2) tables[current_table]['columns'].append(f"{col_name} ({col_type})") @@ -65,16 +69,22 @@ def summarize_schema(file_path): tables[current_table]['pks'].update(pk_cols) continue - # --- Parse Foreign Keys (defined outside CREATE TABLE) --- - match_fk = re.match(r"ALTER TABLE ONLY public\.(\w+)\s+ADD CONSTRAINT \w+ FOREIGN KEY \(([\w,\s\"]+)\) REFERENCES public\.(\w+)\(([\w,\s\"]+)\);", line) - if match_fk: - from_table, from_cols, to_table, to_cols = match_fk.groups() - # Clean up column names - from_cols_clean = ', '.join([c.strip().strip('"') for c in from_cols.split(',')]) - to_cols_clean = ', '.join([c.strip().strip('"') for c in to_cols.split(',')]) - - fk_string = f"({from_cols_clean}) -> {to_table}({to_cols_clean})" - tables[from_table]['fks'].append(fk_string) + # --- Parse Foreign Keys (defined outside CREATE TABLE, may span 2 lines) --- + match_alter = re.match(r"ALTER TABLE ONLY public\.(\w+)$", line) + if match_alter: + pending_alter_table = match_alter.group(1) + continue + + if pending_alter_table: + match_fk = re.match(r"ADD CONSTRAINT \w+ FOREIGN KEY \(([\w,\s\"]+)\) REFERENCES public\.(\w+)\(([\w,\s\"]+)\)", line) + if match_fk: + from_cols, to_table, to_cols = match_fk.groups() + from_cols_clean = ', '.join([c.strip().strip('"') for c in from_cols.split(',')]) + to_cols_clean = ', '.join([c.strip().strip('"') for c in to_cols.split(',')]) + fk_string = f"({from_cols_clean}) -> {to_table}({to_cols_clean})" + tables[pending_alter_table]['fks'].append(fk_string) + pending_alter_table = None + continue # --- Parse Index definitions --- match_index = re.match(r"CREATE (UNIQUE )?INDEX (\w+) ON public\.(\w+) USING (\w+) \((.+)\);", line) @@ -94,44 +104,73 @@ def summarize_schema(file_path): return enums, tables +TYPE_ABBREVIATIONS = { + 'character': 'char', + 'integer': 'int', + 'bigint': 'bigint', + 'smallint': 'smallint', + 'boolean': 'bool', + 'timestamp': 'ts', + 'bytea': 'bytes', + 'real': 'float', + 'json': 'json', + 'jsonb': 'jsonb', + 'text': 'text', + 'uuid': 'uuid', + 'bit(64)': 'bit64', +} + +def shorten_type(col_str): + """Shorten a 'name (type)' string to 'name(short_type)'.""" + match = re.match(r'^(\w+) \((.+)\)$', col_str) + if not match: + return col_str + name, typ = match.group(1), match.group(2) + # Strip public. prefix from enum types + typ = re.sub(r'^public\.', '', typ) + # Apply abbreviations (prefix-based to handle parametrized types like character(64) and array types like integer[]) + for prefix, abbr in TYPE_ABBREVIATIONS.items(): + if typ.startswith(prefix): + typ = abbr + typ[len(prefix):] + break + return f"{name}({typ})" + def format_output(enums, tables): """ - Formats the parsed schema data into a clean, readable string. + Formats the parsed schema data into a compact, LLM-friendly string. """ output = [] - - output.append("### Simplified Database Schema ###") - output.append("\n--- Custom Data Types (ENUMs) ---\n") + + output.append("# Database Schema") + output.append("") + output.append("## ENUMs") if not enums: - output.append("No custom ENUM types found.") + output.append("(none)") else: for name, values in sorted(enums.items()): - output.append(f"{name}:") - for v in values: - output.append(f" - {v}") - output.append("") + output.append(f"{name}: {', '.join(values)}") - output.append("\n--- Tables and Relationships ---\n") + output.append("") + output.append("## Tables") if not tables: - output.append("No tables found.") + output.append("(none)") else: for name, data in sorted(tables.items()): - output.append(f"TABLE: {name}") + # Columns: inline, comma-separated, with PK marker and shortened types + cols = [] for col in data['columns']: + col_short = shorten_type(col) col_name = col.split(' ')[0] - marker = " (PK)" if col_name in data['pks'] else "" - output.append(f" - {col}{marker}") - + if col_name in data['pks']: + col_short += " PK" + cols.append(col_short) + output.append(f"{name}: {', '.join(cols)}") + + # Foreign keys on one indented line if data['fks']: - output.append(" Relationships:") - for fk in data['fks']: - output.append(f" - {fk}") - - if data['indexes']: - output.append(" Indexes:") - for idx in data['indexes']: - output.append(f" - {idx}") - output.append("-" * 20) + output.append(f" FK: {' | '.join(data['fks'])}") + + # Indexes omitted for brevity — query the database for exact index info return "\n".join(output) diff --git a/backend/summarized_schema.txt b/backend/summarized_schema.txt index 5ec7fa7263..0e3d3257df 100644 --- a/backend/summarized_schema.txt +++ b/backend/summarized_schema.txt @@ -1,1088 +1,194 @@ -### Simplified Database Schema ### +# Database Schema ---- Custom Data Types (ENUMs) --- +## ENUMs +action_kind: create, update, delete, execute +asset_access_type: r, w, rw +asset_kind: s3object, resource, variable, ducklake, datatable +asset_usage_kind: script, flow, job +authentication_method: none, windmill, api_key, basic_http, custom_script, signature +autoscaling_event_type: full_scaleout, scalein, scaleout +aws_auth_resource_type: oidc, credentials +delivery_mode: push, pull +draft_type: script, flow, app +favorite_kind: app, script, flow, raw_app, asset +gcp_subscription_mode: create_update, existing +http_method: get, post, put, delete, patch +importer_kind: script, flow, app +job_kind: script, preview, flow, dependencies, flowpreview, script_hub, identity, flowdependencies, http, graphql, postgresql, noop, appdependencies, deploymentcallback, singlestepflow, flowscript, flownode, appscript, aiagent, unassigned_script, unassigned_flow, unassigned_singlestepflow +job_status: success, failure, canceled, skipped +job_trigger_kind: webhook, http, websocket, kafka, email, nats, schedule, app, ui, postgres, sqs, gcp, mqtt, nextcloud +log_mode: standalone, server, worker, agent, indexer, mcp +login_type: password, github +message_type: user, assistant, tool +metric_kind: scalar_int, scalar_float, timeseries_int, timeseries_float +mqtt_client_version: v3, v5 +native_trigger_service: nextcloud +request_type: sync, async, sync_sse +runnable_type: ScriptHash, ScriptPath, FlowPath +script_kind: script, trigger, failure, command, approval, preprocessor +script_lang: python3, deno, go, bash, postgresql, nativets, bun, mysql, bigquery, snowflake, graphql, powershell, mssql, php, bunnative, rust, ansible, csharp, oracledb, nu, java, duckdb, ruby +trigger_kind: webhook, http, websocket, kafka, email, nats, postgres, sqs, mqtt, gcp, default_email, nextcloud +trigger_mode: enabled, disabled, suspended +workspace_key_kind: cloud -action_kind: - - create - - update - - delete - - execute - -authentication_method: - - none - - windmill - - api_key - - basic_http - - custom_script - - signature - -autoscaling_event_type: - - full_scaleout - - scalein - - scaleout - -aws_auth_resource_type: - - oidc - - credentials - -delivery_mode: - - push - - pull - -draft_type: - - script - - flow - - app - -favorite_kind: - - app - - script - - flow - - raw_app - -gcp_subscription_mode: - - create_update - - existing - -http_method: - - get - - post - - put - - delete - - patch - -importer_kind: - - script - - flow - - app - -job_kind: - - script - - preview - - flow - - dependencies - - flowpreview - - script_hub - - identity - - flowdependencies - - http - - graphql - - postgresql - - noop - - appdependencies - - deploymentcallback - - singlescriptflow - - flowscript - - flownode - - appscript - -job_status: - - success - - failure - - canceled - - skipped - -job_trigger_kind: - - webhook - - http - - websocket - - kafka - - email - - nats - - schedule - - app - - ui - - postgres - - sqs - - gcp - -log_mode: - - standalone - - server - - worker - - agent - - indexer - - mcp - -login_type: - - password - - github - -metric_kind: - - scalar_int - - scalar_float - - timeseries_int - - timeseries_float - -mqtt_client_version: - - v3 - - v5 - -runnable_type: - - ScriptHash - - ScriptPath - - FlowPath - -script_kind: - - script - - trigger - - failure - - command - - approval - - preprocessor - -script_lang: - - python3 - - deno - - go - - bash - - postgresql - - nativets - - bun - - mysql - - bigquery - - snowflake - - graphql - - powershell - - mssql - - php - - bunnative - - rust - - ansible - - csharp - - oracledb - - nu - - java - - duckdb - -trigger_kind: - - webhook - - http - - websocket - - kafka - - email - - nats - - postgres - - sqs - - mqtt - - gcp - -workspace_key_kind: - - cloud - - ---- Tables and Relationships --- - -TABLE: _sqlx_migrations - - version (bigint) - - description (text) - - installed_on (timestamp) - - success (boolean) - - checksum (bytea) - - execution_time (bigint) --------------------- -TABLE: account - - workspace_id (character) - - id (integer) - - expires_at (timestamp) - - refresh_token (character) - - client (character) - - refresh_error (text) --------------------- -TABLE: alerts - - id (integer) - - alert_type (character) - - message (text) - - created_at (timestamp) - - acknowledged (boolean) - - workspace_id (text) - - acknowledged_workspace (boolean) - - resource (text) - Indexes: - - INDEX alerts_by_workspace (btree) ON (workspace_id) --------------------- -TABLE: app - - id (bigint) - - workspace_id (character) - - path (character) - - summary (character) - - policy (jsonb) - - versions (bigint[]) - - extra_perms (jsonb) - - draft_only (boolean) - - custom_path (text) - - CONSTRAINT (app_custom_path_check) --------------------- -TABLE: app_script - - id (bigint) - - app (bigint) - - hash (character(64)) - - lock (text) - - code (text) - - code_sha256 (character(64)) --------------------- -TABLE: app_version - - id (bigint) - - app_id (bigint) - - value (json) - - created_by (character) - - created_at (timestamp) - - raw_app (boolean) --------------------- -TABLE: app_version_lite - - id (bigint) - - value (jsonb) --------------------- -TABLE: audit - - workspace_id (character) - - id (integer) - - timestamp (timestamp) - - username (character) - - operation (character) - - action_kind (public.action_kind) - - resource (character) - - parameters (jsonb) - Indexes: - - INDEX ix_audit_timestamps (btree) ON (timestamp DESC) --------------------- -TABLE: autoscaling_event - - id (integer) - - worker_group (text) - - event_type (public.autoscaling_event_type) - - desired_workers (integer) - - applied_at (timestamp) - - reason (text) - Indexes: - - INDEX autoscaling_event_worker_group_idx (btree) ON (worker_group, applied_at) --------------------- -TABLE: capture - - workspace_id (character) - - path (character) - - created_at (timestamp) - - created_by (character) - - main_args (jsonb) - - is_flow (boolean) - - trigger_kind (public.trigger_kind) - - preprocessor_args (jsonb) - - id (bigint) - - CONSTRAINT (capture_payload_too_big) --------------------- -TABLE: capture_config - - workspace_id (character) - - path (character) - - is_flow (boolean) - - trigger_kind (public.trigger_kind) - - trigger_config (jsonb) - - owner (character) - - email (character) - - server_id (character) - - last_client_ping (timestamp) - - last_server_ping (timestamp) - - error (text) --------------------- -TABLE: cloud_workspace_settings - - workspace_id (character) - - threshold_alert_amount (integer) - - last_alert_sent (timestamp) - - last_warning_sent (timestamp) --------------------- -TABLE: concurrency_counter - - concurrency_id (character) - - job_uuids (jsonb) --------------------- -TABLE: concurrency_key - - key (character) - - ended_at (timestamp) - - job_id (uuid) - Indexes: - - INDEX concurrency_key_ended_at_idx (btree) ON (key, ended_at DESC) --------------------- -TABLE: concurrency_locks - - id (character) - - last_locked_at (timestamp) - - owner (character) --------------------- -TABLE: config - - name (character) - - config (jsonb) --------------------- -TABLE: custom_concurrency_key_ended - - key (character) - - ended_at (timestamp) --------------------- -TABLE: dependency_map - - workspace_id (character) - - importer_path (character) - - importer_kind (public.importer_kind) - - imported_path (character) - - importer_node_id (character) - Indexes: - - INDEX dependency_map_imported_path_idx (btree) ON (workspace_id, imported_path) --------------------- -TABLE: deployment_metadata - - workspace_id (character) - - path (character) - - script_hash (bigint) - - app_version (bigint) - - callback_job_ids (uuid[]) - - deployment_msg (text) - - flow_version (bigint) - Indexes: - - UNIQUE INDEX deployment_metadata_app (btree) ON (workspace_id, path, app_version) WHERE (app_version IS NOT NULL) - - UNIQUE INDEX deployment_metadata_flow (btree) ON (workspace_id, path, flow_version) WHERE (flow_version IS NOT NULL) - - UNIQUE INDEX deployment_metadata_script (btree) ON (workspace_id, script_hash) WHERE (script_hash IS NOT NULL) --------------------- -TABLE: draft - - workspace_id (character) - - path (character) - - typ (public.draft_type) - - value (json) - - created_at (timestamp) --------------------- -TABLE: email_to_igroup - - email (character) - - igroup (character) --------------------- -TABLE: favorite - - usr (character) - - workspace_id (character) - - path (character) - - favorite_kind (public.favorite_kind) --------------------- -TABLE: flow - - workspace_id (character) - - path (character) - - summary (text) - - description (text) - - value (jsonb) - - edited_by (character) - - edited_at (timestamp) - - archived (boolean) - - schema (json) - - extra_perms (jsonb) - - dependency_job (uuid) - - draft_only (boolean) - - tag (character) - - ws_error_handler_muted (boolean) - - dedicated_worker (boolean) - - timeout (integer) - - visible_to_runner_only (boolean) - - concurrency_key (character) - - versions (bigint[]) - - on_behalf_of_email (text) - - lock_error_logs (text) - - CONSTRAINT (proper_id) - Indexes: - - INDEX flow_extra_perms (gin) ON (extra_perms) --------------------- -TABLE: flow_node - - id (bigint) - - workspace_id (character) - - hash (bigint) - - path (character) - - lock (text) - - code (text) - - flow (jsonb) - - hash_v2 (character(64)) - Indexes: - - INDEX flow_node_hash (btree) ON (hash) --------------------- -TABLE: flow_version - - id (bigint) - - workspace_id (character) - - path (character) - - value (jsonb) - - schema (json) - - created_by (character) - - created_at (timestamp) - Indexes: - - INDEX index_flow_version_path_created_at (btree) ON (path, created_at) --------------------- -TABLE: flow_version_lite - - id (bigint) - - value (jsonb) --------------------- -TABLE: folder - - name (character) - - workspace_id (character) - - display_name (character) - - owners (character) - - extra_perms (jsonb) - - summary (text) - - edited_at (timestamp) - - created_by (character) - Indexes: - - INDEX folder_extra_perms (gin) ON (extra_perms) - - INDEX folder_owners (gin) ON (owners) --------------------- -TABLE: gcp_trigger - - gcp_resource_path (character) - - topic_id (character) - - subscription_id (character) - - delivery_type (public.delivery_mode) - - delivery_config (jsonb) - - path (character) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - server_id (character) - - last_server_ping (timestamp) - - error (text) - - enabled (boolean) - - subscription_mode (public.gcp_subscription_mode) - - CONSTRAINT (gcp_trigger_check) - - CONSTRAINT (gcp_trigger_subscription_id_check) - - CONSTRAINT (gcp_trigger_topic_id_check) - Indexes: - - UNIQUE INDEX unique_subscription_per_gcp_resource (btree) ON (subscription_id, gcp_resource_path, workspace_id) --------------------- -TABLE: global_settings - - name (character) - - value (jsonb) - - updated_at (timestamp) --------------------- -TABLE: group_ - - workspace_id (character) - - name (character) - - summary (text) - - extra_perms (jsonb) - - CONSTRAINT (proper_name) - Indexes: - - INDEX group_extra_perms (gin) ON (extra_perms) --------------------- -TABLE: healthchecks - - id (bigint) - - check_type (character) - - healthy (boolean) - - created_at (timestamp) - Indexes: - - INDEX healthchecks_check_type_created_at (btree) ON (check_type, created_at) --------------------- -TABLE: http_trigger - - path (character) - - route_path (character) - - route_path_key (character) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - is_async (boolean) - - authentication_method (public.authentication_method) - - http_method (public.http_method) - - static_asset_config (jsonb) - - is_static_website (boolean) - - workspaced_route (boolean) - - wrap_body (boolean) - - raw_string (boolean) - - authentication_resource_path (character) --------------------- -TABLE: input - - id (uuid) - - workspace_id (character) - - runnable_id (character) - - runnable_type (public.runnable_type) - - name (text) - - args (jsonb) - - created_at (timestamp) - - created_by (character) - - is_public (boolean) --------------------- -TABLE: instance_group - - name (character) - - summary (character) - - id (character) - - scim_display_name (character) - - external_id (character) --------------------- -TABLE: job_logs - - job_id (uuid) - - workspace_id (character) - - created_at (timestamp) - - logs (text) - - log_offset (integer) - - log_file_index (text[]) --------------------- -TABLE: job_perms - - job_id (uuid) - - email (character) - - username (character) - - is_admin (boolean) - - is_operator (boolean) - - created_at (timestamp) - - workspace_id (character) - - groups (text[]) - - folders (jsonb[]) --------------------- -TABLE: job_stats - - workspace_id (character) - - job_id (uuid) - - metric_id (character) - - metric_name (character) - - metric_kind (public.metric_kind) - - scalar_int (integer) - - scalar_float (real) - - timestamps (timestamp) - - timeseries_int (integer[]) - - timeseries_float (real[]) - Indexes: - - INDEX job_stats_id (btree) ON (job_id) --------------------- -TABLE: kafka_trigger - - path (character) - - kafka_resource_path (character) - - topics (character) - - group_id (character) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - server_id (character) - - last_server_ping (timestamp) - - error (text) - - enabled (boolean) --------------------- -TABLE: log_file - - hostname (character) - - log_ts (timestamp) - - ok_lines (bigint) - - err_lines (bigint) - - mode (public.log_mode) - - worker_group (character) - - file_path (character) - - json_fmt (boolean) - Indexes: - - INDEX log_file_log_ts_idx (btree) ON (log_ts) --------------------- -TABLE: magic_link - - email (character) - - token (character) - - expiration (timestamp) - Indexes: - - INDEX index_magic_link_exp (btree) ON (expiration) --------------------- -TABLE: metrics - - id (character) - - value (jsonb) - - created_at (timestamp) - Indexes: - - INDEX idx_metrics_id_created_at (btree) ON (id, created_at DESC) WHERE ((id)::text ~~ 'queue_%'::text) - - INDEX metrics_key_idx (btree) ON (id) - - INDEX metrics_sort_idx (btree) ON (created_at DESC) --------------------- -TABLE: mqtt_trigger - - mqtt_resource_path (character) - - subscribe_topics (jsonb[]) - - client_version (public.mqtt_client_version) - - v5_config (jsonb) - - v3_config (jsonb) - - client_id (character) - - path (character) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - server_id (character) - - last_server_ping (timestamp) - - error (text) - - enabled (boolean) --------------------- -TABLE: nats_trigger - - path (character) - - nats_resource_path (character) - - subjects (character) - - stream_name (character) - - consumer_name (character) - - use_jetstream (boolean) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - server_id (character) - - last_server_ping (timestamp) - - error (text) - - enabled (boolean) --------------------- -TABLE: outstanding_wait_time - - job_id (uuid) - - self_wait_time_ms (bigint) - - aggregate_wait_time_ms (bigint) --------------------- -TABLE: parallel_monitor_lock - - parent_flow_id (uuid) - - job_id (uuid) - - last_ping (timestamp) --------------------- -TABLE: password - - email (character) - - password_hash (character) - - login_type (character) - - super_admin (boolean) - - verified (boolean) - - name (character) - - company (character) - - first_time_user (boolean) - - username (character) - - devops (boolean) --------------------- -TABLE: pending_user - - email (character) - - created_at (timestamp) - - username (character) --------------------- -TABLE: pip_resolution_cache - - hash (character) - - expiration (timestamp) - - lockfile (text) --------------------- -TABLE: postgres_trigger - - path (character) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - postgres_resource_path (character) - - error (text) - - server_id (character) - - last_server_ping (timestamp) - - replication_slot_name (character) - - publication_name (character) - - enabled (boolean) --------------------- -TABLE: raw_app - - path (character) - - version (integer) - - workspace_id (character) - - summary (character) - - edited_at (timestamp) - - data (text) - - extra_perms (jsonb) --------------------- -TABLE: resource - - workspace_id (character) - - path (character) - - value (jsonb) - - description (text) - - resource_type (character) - - extra_perms (jsonb) - - edited_at (timestamp) - - created_by (character) - - CONSTRAINT (proper_id) - Indexes: - - INDEX resource_extra_perms (gin) ON (extra_perms) --------------------- -TABLE: resource_type - - workspace_id (character) - - name (character) - - schema (jsonb) - - description (text) - - edited_at (timestamp) - - created_by (character) - - format_extension (character) - - CONSTRAINT (proper_name) --------------------- -TABLE: resume_job - - id (uuid) - - job (uuid) - - flow (uuid) - - created_at (timestamp) - - value (jsonb) - - approver (character) - - resume_id (integer) - - approved (boolean) --------------------- -TABLE: schedule - - workspace_id (character) - - path (character) - - edited_by (character) - - edited_at (timestamp) - - schedule (character) - - enabled (boolean) - - script_path (character) - - args (jsonb) - - extra_perms (jsonb) - - is_flow (boolean) - - email (character) - - error (text) - - timezone (character) - - on_failure (character) - - on_recovery (character) - - on_failure_times (integer) - - on_failure_exact (boolean) - - on_failure_extra_args (jsonb) - - on_recovery_times (integer) - - on_recovery_extra_args (jsonb) - - ws_error_handler_muted (boolean) - - retry (jsonb) - - summary (character) - - no_flow_overlap (boolean) - - tag (character) - - paused_until (timestamp) - - on_success (character) - - on_success_extra_args (jsonb) - - cron_version (text) - - description (text) - - CONSTRAINT (proper_id) - Indexes: - - INDEX schedule_extra_perms (gin) ON (extra_perms) --------------------- -TABLE: script - - workspace_id (character) - - hash (bigint) - - path (character) - - parent_hashes (bigint[]) - - summary (text) - - description (text) - - content (text) - - created_by (character) - - created_at (timestamp) - - archived (boolean) - - schema (json) - - deleted (boolean) - - is_template (boolean) - - extra_perms (jsonb) - - lock (text) - - lock_error_logs (text) - - language (public.script_lang) - - kind (public.script_kind) - - tag (character) - - draft_only (boolean) - - envs (character) - - concurrent_limit (integer) - - concurrency_time_window_s (integer) - - cache_ttl (integer) - - dedicated_worker (boolean) - - ws_error_handler_muted (boolean) - - priority (smallint) - - timeout (integer) - - delete_after_use (boolean) - - restart_unless_cancelled (boolean) - - concurrency_key (character) - - visible_to_runner_only (boolean) - - no_main_func (boolean) - - codebase (character) - - has_preprocessor (boolean) - - on_behalf_of_email (text) - - schema_validation (boolean) - - CONSTRAINT (proper_id) - Indexes: - - INDEX index_script_on_path_created_at (btree) ON (workspace_id, path, created_at DESC) - - INDEX script_extra_perms (gin) ON (extra_perms) --------------------- -TABLE: sqs_trigger - - path (character) - - queue_url (character) - - aws_resource_path (character) - - message_attributes (text[]) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - error (text) - - server_id (character) - - last_server_ping (timestamp) - - enabled (boolean) - - aws_auth_resource_type (public.aws_auth_resource_type) --------------------- -TABLE: token - - token (character) - - label (character) - - expiration (timestamp) - - workspace_id (character) - - owner (character) - - email (character) - - super_admin (boolean) - - created_at (timestamp) - - last_used_at (timestamp) - - scopes (text[]) - - job (uuid) - Indexes: - - INDEX index_token_exp (btree) ON (expiration) --------------------- -TABLE: tutorial_progress - - email (character) - - progress (bit(64)) --------------------- -TABLE: usage - - id (character) - - is_workspace (boolean) - - month_ (integer) - - usage (integer) --------------------- -TABLE: usr - - workspace_id (character) - - username (character) - - email (character) - - is_admin (boolean) - - created_at (timestamp) - - operator (boolean) - - disabled (boolean) - - role (character) - - CONSTRAINT (proper_email) - - CONSTRAINT (proper_username) - Indexes: - - INDEX index_usr_email (btree) ON (email) --------------------- -TABLE: usr_to_group - - workspace_id (character) - - group_ (character) - - usr (character) --------------------- -TABLE: v2_job - - id (uuid) - - raw_code (text) - - raw_lock (text) - - raw_flow (jsonb) - - tag (character) - - workspace_id (character) - - created_at (timestamp) - - created_by (character) - - permissioned_as (character) - - permissioned_as_email (character) - - kind (public.job_kind) - - runnable_id (bigint) - - runnable_path (character) - - parent_job (uuid) - - root_job (uuid) - - script_lang (public.script_lang) - - script_entrypoint_override (character) - - flow_step (integer) - - flow_step_id (character) - - flow_innermost_root_job (uuid) - - trigger (character) - - trigger_kind (public.job_trigger_kind) - - same_worker (boolean) - - visible_to_owner (boolean) - - concurrent_limit (integer) - - concurrency_time_window_s (integer) - - cache_ttl (integer) - - timeout (integer) - - priority (smallint) - - preprocessed (boolean) - - args (jsonb) - - labels (text[]) - - pre_run_error (text) - Indexes: - - INDEX ix_job_created_at (btree) ON (created_at DESC) - - INDEX ix_job_root_job_index_by_path_2 (btree) ON (workspace_id, runnable_path, created_at DESC) WHERE (parent_job IS NULL) - - INDEX ix_job_workspace_id_created_at_new_3 (btree) ON (workspace_id, created_at DESC) - - INDEX ix_job_workspace_id_created_at_new_5 (btree) ON (workspace_id, created_at DESC) WHERE ((kind = ANY (ARRAY['preview'::public.job_kind, 'flowpreview'::public.job_kind])) AND (parent_job IS NULL)) - - INDEX ix_job_workspace_id_created_at_new_8 (btree) ON (workspace_id, created_at DESC) WHERE ((kind = 'deploymentcallback'::public.job_kind) AND (parent_job IS NULL)) - - INDEX ix_job_workspace_id_created_at_new_9 (btree) ON (workspace_id, created_at DESC) WHERE ((kind = ANY (ARRAY['dependencies'::public.job_kind, 'flowdependencies'::public.job_kind, 'appdependencies'::public.job_kind])) AND (parent_job IS NULL)) - - INDEX ix_v2_job_labels (gin) ON (labels) WHERE (labels IS NOT NULL) - - INDEX ix_v2_job_workspace_id_created_at (btree) ON (workspace_id, created_at DESC) WHERE ((kind = ANY (ARRAY['script'::public.job_kind, 'flow'::public.job_kind, 'singlescriptflow'::public.job_kind])) AND (parent_job IS NULL)) --------------------- -TABLE: v2_job_completed - - id (uuid) - - workspace_id (character) - - duration_ms (bigint) - - result (jsonb) - - deleted (boolean) - - canceled_by (character) - - canceled_reason (text) - - flow_status (jsonb) - - started_at (timestamp) - - memory_peak (integer) - - status (public.job_status) - - completed_at (timestamp) - - worker (character) - - workflow_as_code_status (jsonb) - - result_columns (text[]) - - retries (uuid[]) - - extras (jsonb) - Indexes: - - INDEX ix_completed_job_workspace_id_started_at_new_2 (btree) ON (workspace_id, started_at DESC) - - INDEX ix_job_completed_completed_at (btree) ON (completed_at DESC) - - INDEX labeled_jobs_on_jobs (gin) ON (((result -> 'wm_labels'::text))) WHERE (result ? 'wm_labels'::text) --------------------- -TABLE: v2_job_queue - - id (uuid) - - workspace_id (character) - - created_at (timestamp) - - started_at (timestamp) - - scheduled_for (timestamp) - - running (boolean) - - canceled_by (character) - - canceled_reason (text) - - suspend (integer) - - suspend_until (timestamp) - - tag (character) - - priority (smallint) - - worker (character) - - extras (jsonb) - Indexes: - - INDEX queue_sort_v2 (btree) ON (priority DESC NULLS LAST, scheduled_for, tag) WHERE (running = false) - - INDEX queue_suspended (btree) ON (priority DESC NULLS LAST, created_at, suspend_until, suspend, tag) WHERE (suspend_until IS NOT NULL) - - INDEX root_queue_index_by_path (btree) ON (workspace_id, created_at) - - INDEX v2_job_queue_suspend (btree) ON (workspace_id, suspend) WHERE (suspend > 0) --------------------- -TABLE: v2_job_runtime - - id (uuid) - - ping (timestamp) - - memory_peak (integer) --------------------- -TABLE: v2_job_status - - id (uuid) - - flow_status (jsonb) - - flow_leaf_jobs (jsonb) - - workflow_as_code_status (jsonb) --------------------- -TABLE: variable - - workspace_id (character) - - path (character) - - value (character) - - is_secret (boolean) - - description (character) - - extra_perms (jsonb) - - account (integer) - - is_oauth (boolean) - - expires_at (timestamp) - - CONSTRAINT (proper_id) - Indexes: - - INDEX variable_extra_perms (gin) ON (extra_perms) --------------------- -TABLE: websocket_trigger - - path (character) - - url (character) - - script_path (character) - - is_flow (boolean) - - workspace_id (character) - - edited_by (character) - - email (character) - - edited_at (timestamp) - - extra_perms (jsonb) - - server_id (character) - - last_server_ping (timestamp) - - error (text) - - enabled (boolean) - - filters (jsonb[]) - - initial_messages (jsonb[]) - - url_runnable_args (jsonb) - - can_return_message (boolean) --------------------- -TABLE: windmill_migrations - - name (text) - - created_at (timestamp) --------------------- -TABLE: worker_ping - - worker (character) - - worker_instance (character) - - ping_at (timestamp) - - started_at (timestamp) - - ip (character) - - jobs_executed (integer) - - custom_tags (text[]) - - worker_group (character) - - dedicated_worker (character) - - dedicated_workers (text[]) - - wm_version (character) - - current_job_id (uuid) - - current_job_workspace_id (character) - - vcpus (bigint) - - memory (bigint) - - occupancy_rate (real) - - memory_usage (bigint) - - wm_memory_usage (bigint) - - occupancy_rate_15s (real) - - occupancy_rate_5m (real) - - occupancy_rate_30m (real) - Indexes: - - INDEX worker_ping_on_ping_at (btree) ON (ping_at) --------------------- -TABLE: workspace - - id (character) - - name (character) - - owner (character) - - deleted (boolean) - - premium (boolean) - - CONSTRAINT (proper_id) --------------------- -TABLE: workspace_env - - workspace_id (character) - - name (character) - - value (character) --------------------- -TABLE: workspace_invite - - workspace_id (character) - - email (character) - - is_admin (boolean) - - operator (boolean) - - CONSTRAINT (proper_email) --------------------- -TABLE: workspace_key - - workspace_id (character) - - kind (public.workspace_key_kind) - - key (character) --------------------- -TABLE: workspace_runnable_dependencies - - flow_path (character) - - runnable_path (character) - - script_hash (bigint) - - runnable_is_flow (boolean) - - workspace_id (character) - - app_path (character) - - CONSTRAINT (workspace_runnable_dependencies_path_exclusive) - Indexes: - - UNIQUE INDEX app_workspace_with_hash_unique_idx (btree) ON (app_path, runnable_path, script_hash, runnable_is_flow, workspace_id) WHERE (script_hash IS NOT NULL) - - UNIQUE INDEX app_workspace_without_hash_unique_idx (btree) ON (app_path, runnable_path, runnable_is_flow, workspace_id) WHERE (script_hash IS NULL) - - INDEX flow_workspace_runnable_path_is_flow_idx (btree) ON (runnable_path, runnable_is_flow, workspace_id) - - UNIQUE INDEX flow_workspace_with_hash_unique_idx (btree) ON (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) WHERE (script_hash IS NOT NULL) - - UNIQUE INDEX flow_workspace_without_hash_unique_idx (btree) ON (flow_path, runnable_path, runnable_is_flow, workspace_id) WHERE (script_hash IS NULL) --------------------- -TABLE: workspace_settings - - workspace_id (character) - - slack_team_id (character) - - slack_name (character) - - slack_command_script (character) - - slack_email (character) - - auto_invite_domain (character) - - auto_invite_operator (boolean) - - customer_id (character) - - plan (character) - - webhook (text) - - deploy_to (character) - - error_handler (character) - - ai_config (jsonb) - - error_handler_extra_args (json) - - error_handler_muted_on_cancel (boolean) - - success_handler (text) - - success_handler_extra_args (json) - - large_file_storage (jsonb) - - git_sync (jsonb) - - default_app (character) - - auto_add (boolean) - - default_scripts (jsonb) - - deploy_ui (jsonb) - - mute_critical_alerts (boolean) - - color (character) - - operator_settings (jsonb) - - teams_command_script (text) - - teams_team_id (text) - - teams_team_name (text) - - git_app_installations (jsonb) --------------------- -TABLE: zombie_job_counter - - job_id (uuid) - - counter (integer) --------------------- +## Tables +_sqlx_migrations: version(bigint), description(text), installed_on(ts), success(bool), checksum(bytes), execution_time(bigint) +account: workspace_id(char), id(int), expires_at(ts), refresh_token(char), client(char), refresh_error(text), grant_type(char), cc_client_id(char), cc_client_secret(char), cc_token_url(char), mcp_server_url(text) + FK: (workspace_id) -> workspace(id) +agent_token_blacklist: token(char), expires_at(ts), blacklisted_at(ts), blacklisted_by(char) +ai_agent_memory: workspace_id(char), conversation_id(uuid), step_id(char), messages(jsonb), created_at(ts), updated_at(ts) +alerts: id(int), alert_type(char), message(text), created_at(ts), acknowledged(bool), workspace_id(text), acknowledged_workspace(bool), resource(text) +app: id(bigint), workspace_id(char), path(char), summary(char), policy(jsonb), versions(bigint[]), extra_perms(jsonb), draft_only(bool), custom_path(text) + FK: (workspace_id) -> workspace(id) +app_bundles: app_version_id(bigint), w_id(char), file_type(char), data(bytes) +app_script: id(bigint), app(bigint), hash(char(64)), lock(text), code(text), code_sha256(char(64)) + FK: (app) -> app(id) +app_version: id(bigint), app_id(bigint), value(json), created_by(char), created_at(ts), raw_app(bool) + FK: (app_id) -> app(id) +app_version_lite: id(bigint), value(jsonb) + FK: (id) -> app_version(id) +asset: workspace_id(char), path(char), kind(asset_kind), usage_access_type(asset_access_type), usage_path(char), usage_kind(asset_usage_kind), created_at(ts), id(bigint), columns(jsonb) + FK: (workspace_id) -> workspace(id) +audit: workspace_id(char), id(bigint), timestamp(ts), username(char), operation(char), action_kind(action_kind), resource(char), parameters(jsonb), email(char), span(char) +autoscaling_event: id(int), worker_group(text), event_type(autoscaling_event_type), desired_workers(int), applied_at(ts), reason(text) +capture: workspace_id(char), path(char), created_at(ts), created_by(char), main_args(jsonb), is_flow(bool), trigger_kind(trigger_kind), preprocessor_args(jsonb), id(bigint) + FK: (workspace_id) -> workspace(id) +capture_config: workspace_id(char), path(char), is_flow(bool), trigger_kind(trigger_kind), trigger_config(jsonb), owner(char), email(char), server_id(char), last_client_ping(ts), last_server_ping(ts), error(text) + FK: (workspace_id) -> workspace(id) +cloud_workspace_settings: workspace_id(char), threshold_alert_amount(int), last_alert_sent(ts), last_warning_sent(ts), is_past_due(bool), max_tolerated_executions(int) + FK: (workspace_id) -> workspace(id) +concurrency_counter: concurrency_id(char), job_uuids(jsonb) +concurrency_key: key(char), ended_at(ts), job_id(uuid) +concurrency_locks: id(char), last_locked_at(ts), owner(char) +concurrency_settings: hash(bigint), concurrency_key(char), concurrent_limit(int), concurrency_time_window_s(int) +config: name(char), config(jsonb) +custom_concurrency_key_ended: key(char), ended_at(ts) +debounce_key: key(char), job_id(uuid), previous_job_id(uuid), first_started_at(ts), debounced_times(int) +debounce_stale_data: job_id(uuid), to_relock(text[]) +debouncing_settings: hash(bigint), debounce_key(char), debounce_delay_s(int), max_total_debouncing_time(int), max_total_debounces_amount(int), debounce_args_to_accumulate(text[]) +dependency_map: workspace_id(char), importer_path(char), importer_kind(importer_kind), imported_path(char), importer_node_id(char) +deployment_metadata: workspace_id(char), path(char), script_hash(bigint), app_version(bigint), callback_job_ids(uuid[]), deployment_msg(text), flow_version(bigint), job_id(uuid) + FK: (workspace_id) -> workspace(id) +draft: workspace_id(char), path(char), typ(draft_type), value(json), created_at(ts) + FK: (workspace_id) -> workspace(id) +email_to_igroup: email(char), igroup(char) +email_trigger: path(char), local_part(char), workspaced_local_part(bool), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode) +favorite: usr(char), workspace_id(char), path(char), favorite_kind(favorite_kind) +flow: workspace_id(char), path(char), summary(text), description(text), value(jsonb), edited_by(char), edited_at(ts), archived(bool), schema(json), extra_perms(jsonb), dependency_job(uuid), draft_only(bool), tag(char), ws_error_handler_muted(bool), dedicated_worker(bool), timeout(int), visible_to_runner_only(bool), concurrency_key(char), versions(bigint[]), on_behalf_of_email(text), lock_error_logs(text) + FK: (workspace_id) -> workspace(id) +flow_conversation: id(uuid), workspace_id(char), flow_path(char), title(char), created_at(ts), updated_at(ts), created_by(char) + FK: (workspace_id) -> workspace(id) +flow_conversation_message: id(uuid), conversation_id(uuid), message_type(message_type), content(text), job_id(uuid), created_at(ts), step_name(char), success(bool) + FK: (conversation_id) -> flow_conversation(id) | (job_id) -> v2_job(id) +flow_iterator_data: job_id(uuid), itered(jsonb) +flow_node: id(bigint), workspace_id(char), hash(bigint), path(char), lock(text), code(text), flow(jsonb), hash_v2(char(64)) + FK: (path, workspace_id) -> flow(path, workspace_id) | (workspace_id) -> workspace(id) +flow_version: id(bigint), workspace_id(char), path(char), value(jsonb), schema(json), created_by(char), created_at(ts) + FK: (workspace_id, path) -> flow(workspace_id, path) +flow_version_lite: id(bigint), value(jsonb) + FK: (id) -> flow_version(id) +folder: name(char), workspace_id(char), display_name(char), owners(char), extra_perms(jsonb), summary(text), edited_at(ts), created_by(char) + FK: (workspace_id) -> workspace(id) +folder_permission_history: id(bigint), workspace_id(char), folder_name(char), changed_by(char), changed_at(ts), change_type(char), affected(char) + FK: (workspace_id, folder_name) -> folder(workspace_id, name) +gcp_trigger: gcp_resource_path(char), topic_id(char), subscription_id(char), delivery_type(delivery_mode), delivery_config(jsonb), path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), subscription_mode(gcp_subscription_mode), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), auto_acknowledge_msg(bool), ack_deadline(int), mode(trigger_mode) +global_settings: name(char), value(jsonb), updated_at(ts) +group_: workspace_id(char), name(char), summary(text), extra_perms(jsonb) + FK: (workspace_id) -> workspace(id) +group_permission_history: id(bigint), workspace_id(char), group_name(char), changed_by(char), changed_at(ts), change_type(char), member_affected(char) + FK: (workspace_id, group_name) -> group_(workspace_id, name) +healthchecks: id(bigint), check_type(char), healthy(bool), created_at(ts) +http_trigger: path(char), route_path(char), route_path_key(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), authentication_method(authentication_method), http_method(http_method), static_asset_config(jsonb), is_static_website(bool), workspaced_route(bool), wrap_body(bool), raw_string(bool), authentication_resource_path(char), summary(char), description(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), request_type(request_type), mode(trigger_mode) +input: id(uuid), workspace_id(char), runnable_id(char), runnable_type(runnable_type), name(text), args(jsonb), created_at(ts), created_by(char), is_public(bool) + FK: (workspace_id) -> workspace(id) +instance_group: name(char), summary(char), id(char), scim_display_name(char), external_id(char) +job_logs: job_id(uuid), workspace_id(char), created_at(ts), logs(text), log_offset(int), log_file_index(text[]) +job_perms: job_id(uuid), email(char), username(char), is_admin(bool), is_operator(bool), created_at(ts), workspace_id(char), groups(text[]), folders(jsonb[]), end_user_email(char) +job_result_stream: job_id(uuid), workspace_id(text), stream(text) +job_result_stream_v2: job_id(uuid), workspace_id(text), stream(text), idx(int) +job_settings: job_id(uuid), runnable_settings(bigint) +job_stats: workspace_id(char), job_id(uuid), metric_id(char), metric_name(char), metric_kind(metric_kind), scalar_int(int), scalar_float(float), timestamps(ts), timeseries_int(int[]), timeseries_float(float[]) + FK: (workspace_id) -> workspace(id) +kafka_trigger: path(char), kafka_resource_path(char), topics(char), group_id(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), filters(jsonb[]) +log_file: hostname(char), log_ts(ts), ok_lines(bigint), err_lines(bigint), mode(log_mode), worker_group(char), file_path(char), json_fmt(bool) +magic_link: email(char), token(char), expiration(ts) +mcp_oauth_client: mcp_server_url(text), client_id(text), client_secret(text), client_secret_expires_at(ts), token_endpoint(text), created_at(ts) +mcp_oauth_refresh_token: id(bigint), refresh_token(char), access_token(char), client_id(char), user_email(char), workspace_id(char), scopes(text[]), token_family(uuid), created_at(ts), expires_at(ts), used_at(ts), revoked(bool) + FK: (client_id) -> mcp_oauth_server_client(client_id) +mcp_oauth_server_client: client_id(char), client_name(char), redirect_uris(text[]), created_at(ts) +mcp_oauth_server_code: code(char), client_id(char), user_email(char), workspace_id(char), scopes(text[]), redirect_uri(text), code_challenge(char), code_challenge_method(char), created_at(ts), expires_at(ts) + FK: (client_id) -> mcp_oauth_server_client(client_id) +metrics: id(char), value(jsonb), created_at(ts) +mqtt_trigger: mqtt_resource_path(char), subscribe_topics(jsonb[]), client_version(mqtt_client_version), v5_config(jsonb), v3_config(jsonb), client_id(char), path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode) +native_trigger: external_id(char), workspace_id(char), service_name(native_trigger_service), script_path(char), is_flow(bool), webhook_token_prefix(char), service_config(jsonb), error(text), created_at(ts), updated_at(ts) + FK: (workspace_id) -> workspace(id) +nats_trigger: path(char), nats_resource_path(char), subjects(char), stream_name(char), consumer_name(char), use_jetstream(bool), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode) + FK: (workspace_id) -> workspace(id) +notify_event: id(bigint), channel(text), payload(text), created_at(ts) +otel_traces: trace_id(bytes), span_id(bytes), trace_state(text), parent_span_id(bytes), flags(int), name(text), kind(int), start_time_unix_nano(bigint), end_time_unix_nano(bigint), attributes(jsonb), dropped_attributes_count(int), events(jsonb), dropped_events_count(int), links(jsonb), dropped_links_count(int), status(jsonb) +outstanding_wait_time: job_id(uuid), self_wait_time_ms(bigint), aggregate_wait_time_ms(bigint) +parallel_monitor_lock: parent_flow_id(uuid), job_id(uuid), last_ping(ts) +password: email(char), password_hash(char), login_type(char), super_admin(bool), verified(bool), name(char), company(char), first_time_user(bool), username(char), devops(bool) +pending_user: email(char), created_at(ts), username(char) +pip_resolution_cache: hash(char), expiration(ts), lockfile(text) +postgres_trigger: path(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), postgres_resource_path(char), error(text), server_id(char), last_server_ping(ts), replication_slot_name(char), publication_name(char), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode) + FK: (workspace_id) -> workspace(id) +raw_app: path(char), version(int), workspace_id(char), summary(char), edited_at(ts), data(text), extra_perms(jsonb) + FK: (workspace_id) -> workspace(id) +resource: workspace_id(char), path(char), value(jsonb), description(text), resource_type(char), extra_perms(jsonb), edited_at(ts), created_by(char) + FK: (workspace_id) -> workspace(id) +resource_type: workspace_id(char), name(char), schema(jsonb), description(text), edited_at(ts), created_by(char), format_extension(char) + FK: (workspace_id) -> workspace(id) +resume_job: id(uuid), job(uuid), flow(uuid), created_at(ts), value(jsonb), approver(char), resume_id(int), approved(bool) + FK: (flow) -> v2_job_queue(id) +runnable_settings: hash(bigint), debouncing_settings(bigint), concurrency_settings(bigint) +schedule: workspace_id(char), path(char), edited_by(char), edited_at(ts), schedule(char), enabled(bool), script_path(char), args(jsonb), extra_perms(jsonb), is_flow(bool), email(char), error(text), timezone(char), on_failure(char), on_recovery(char), on_failure_times(int), on_failure_exact(bool), on_failure_extra_args(jsonb), on_recovery_times(int), on_recovery_extra_args(jsonb), ws_error_handler_muted(bool), retry(jsonb), summary(char), no_flow_overlap(bool), tag(char), paused_until(ts), on_success(char), on_success_extra_args(jsonb), cron_version(text), description(text), dynamic_skip(char) + FK: (workspace_id) -> workspace(id) +script: workspace_id(char), hash(bigint), path(char), parent_hashes(bigint[]), summary(text), description(text), content(text), created_by(char), created_at(ts), archived(bool), schema(json), deleted(bool), is_template(bool), extra_perms(jsonb), lock(text), lock_error_logs(text), language(script_lang), kind(script_kind), tag(char), draft_only(bool), envs(char), concurrent_limit(int), concurrency_time_window_s(int), cache_ttl(int), dedicated_worker(bool), ws_error_handler_muted(bool), priority(smallint), timeout(int), delete_after_use(bool), restart_unless_cancelled(bool), concurrency_key(char), visible_to_runner_only(bool), no_main_func(bool), codebase(char), has_preprocessor(bool), on_behalf_of_email(text), schema_validation(bool), assets(jsonb), debounce_key(char), debounce_delay_s(int), cache_ignore_s3_path(bool), runnable_settings_handle(bigint) + FK: (workspace_id) -> workspace(id) +skip_workspace_diff_tally: workspace_id(char), added_at(ts) +sqs_trigger: path(char), queue_url(char), aws_resource_path(char), message_attributes(text[]), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), error(text), server_id(char), last_server_ping(ts), aws_auth_resource_type(aws_auth_resource_type), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode) + FK: (workspace_id) -> workspace(id) +token: token(char), label(char), expiration(ts), workspace_id(char), owner(char), email(char), super_admin(bool), created_at(ts), last_used_at(ts), scopes(text[]), job(uuid) + FK: (workspace_id) -> workspace(id) +tutorial_progress: email(char), progress(bit64), skipped_all(bool) +unique_ext_jwt_token: jwt_hash(bigint), last_used_at(ts) +usage: id(char), is_workspace(bool), month_(int), usage(int) +usr: workspace_id(char), username(char), email(char), is_admin(bool), created_at(ts), operator(bool), disabled(bool), role(char), added_via(jsonb) + FK: (workspace_id) -> workspace(id) +usr_to_group: workspace_id(char), group_(char), usr(char) + FK: (workspace_id, group_) -> group_(workspace_id, name) | (workspace_id) -> workspace(id) +v2_job: id(uuid), raw_code(text), raw_lock(text), raw_flow(jsonb), tag(char), workspace_id(char), created_at(ts), created_by(char), permissioned_as(char), permissioned_as_email(char), kind(job_kind), runnable_id(bigint), runnable_path(char), parent_job(uuid), root_job(uuid), script_lang(script_lang), script_entrypoint_override(char), flow_step(int), flow_step_id(char), flow_innermost_root_job(uuid), trigger(char), trigger_kind(job_trigger_kind), same_worker(bool), visible_to_owner(bool), concurrent_limit(int), concurrency_time_window_s(int), cache_ttl(int), timeout(int), priority(smallint), preprocessed(bool), args(jsonb), labels(text[]), pre_run_error(text) +v2_job_completed: id(uuid), workspace_id(char), duration_ms(bigint), result(jsonb), deleted(bool), canceled_by(char), canceled_reason(text), flow_status(jsonb), started_at(ts), memory_peak(int), status(job_status), completed_at(ts), worker(char), workflow_as_code_status(jsonb), result_columns(text[]), retries(uuid[]), extras(jsonb) +v2_job_debounce_batch: id(uuid), debounce_batch(bigint) +v2_job_queue: id(uuid), workspace_id(char), created_at(ts), started_at(ts), scheduled_for(ts), running(bool), canceled_by(char), canceled_reason(text), suspend(int), suspend_until(ts), tag(char), priority(smallint), worker(char), extras(jsonb), cache_ignore_s3_path(bool), runnable_settings_handle(bigint) +v2_job_runtime: id(uuid), ping(ts), memory_peak(int) + FK: (id) -> v2_job_queue(id) +v2_job_status: id(uuid), flow_status(jsonb), flow_leaf_jobs(jsonb), workflow_as_code_status(jsonb) + FK: (id) -> v2_job_queue(id) +variable: workspace_id(char), path(char), value(char), is_secret(bool), description(char), extra_perms(jsonb), account(int), is_oauth(bool), expires_at(ts) + FK: (workspace_id) -> workspace(id) +websocket_trigger: path(char), url(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), filters(jsonb[]), initial_messages(jsonb[]), url_runnable_args(jsonb), can_return_message(bool), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), can_return_error_result(bool), mode(trigger_mode) +windmill_migrations: name(text), created_at(ts) +worker_group_job_stats: hour(bigint), worker_group(text), script_lang(char), workspace_id(char), job_count(int), total_duration_ms(bigint) + FK: (workspace_id) -> workspace(id) +worker_ping: worker(char), worker_instance(char), ping_at(ts), started_at(ts), ip(char), jobs_executed(int), custom_tags(text[]), worker_group(char), dedicated_worker(char), wm_version(char), current_job_id(uuid), current_job_workspace_id(char), vcpus(bigint), memory(bigint), occupancy_rate(float), memory_usage(bigint), wm_memory_usage(bigint), occupancy_rate_15s(float), occupancy_rate_5m(float), occupancy_rate_30m(float), job_isolation(text), dedicated_workers(text[]) +workspace: id(char), name(char), owner(char), deleted(bool), premium(bool), parent_workspace_id(char) + FK: (parent_workspace_id) -> workspace(id) +workspace_dependencies: id(bigint), name(char), content(text), language(script_lang), description(text), archived(bool), workspace_id(char), created_at(ts) +workspace_diff: source_workspace_id(char), fork_workspace_id(char), path(char), kind(char), ahead(int), behind(int), has_changes(bool), exists_in_source(bool), exists_in_fork(bool) +workspace_env: workspace_id(char), name(char), value(char) +workspace_integrations: workspace_id(char), service_name(native_trigger_service), oauth_data(jsonb), created_at(ts), updated_at(ts), created_by(char) + FK: (workspace_id) -> workspace(id) +workspace_invite: workspace_id(char), email(char), is_admin(bool), operator(bool) + FK: (workspace_id) -> workspace(id) +workspace_key: workspace_id(char), kind(workspace_key_kind), key(char) + FK: (workspace_id) -> workspace(id) +workspace_protection_rule: workspace_id(char), name(char), rules(int), bypass_groups(text[]), bypass_users(text[]), created_at(ts) + FK: (workspace_id) -> workspace(id) +workspace_runnable_dependencies: flow_path(char), runnable_path(char), script_hash(bigint), runnable_is_flow(bool), workspace_id(char), app_path(char) + FK: (app_path, workspace_id) -> app(path, workspace_id) | (flow_path, workspace_id) -> flow(path, workspace_id) +workspace_settings: workspace_id(char), slack_team_id(char), slack_name(char), slack_command_script(char), slack_email(char), customer_id(char), plan(char), webhook(text), deploy_to(char), ai_config(jsonb), large_file_storage(jsonb), git_sync(jsonb), default_app(char), default_scripts(jsonb), deploy_ui(jsonb), mute_critical_alerts(bool), color(char), operator_settings(jsonb), teams_command_script(text), teams_team_id(text), teams_team_name(text), git_app_installations(jsonb), ducklake(jsonb), slack_oauth_client_id(char), slack_oauth_client_secret(char), datatable(jsonb), teams_team_guid(text), auto_invite(jsonb), error_handler(jsonb), success_handler(jsonb), public_app_execution_limit_per_minute(int) + FK: (workspace_id) -> workspace(id) +zombie_job_counter: job_id(uuid), counter(int) + FK: (job_id) -> v2_job(id) diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 6788ac10be..35fa075087 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -2,7 +2,7 @@ ## Core Principles -- Follow @svelte5-best-practices.mdc for detailed guidelines +- Follow the `svelte-frontend` skill for best practices: .claude/skills/svelte-frontend/SKILL.md - Use Runes ($state, $derived, $effect) for reactivity - Keep components small and focused - Always use keys in {#each} blocks @@ -93,6 +93,14 @@ The `resource()` utility: - Form components (TextInputs, ToggleButtons, Select ...) should all use the same size when put together, using the unified size system. - Read carefully components props JSDoc before using them +## Code Validation (MUST DO) + +After making frontend changes, you MUST run the following and fix all errors and warnings before considering the work done: + +```bash +npm run check +``` + ## Backend API - If you need to call the backend API, you can find the available routes in ../backend/windmill-api/openapi.yaml From 647316dbf291fceceea18ecfa296c021205a225b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 12 Feb 2026 09:45:51 +0000 Subject: [PATCH 24/93] better krb5 dockerfile setup --- Dockerfile | 4 +++- docker/DockerfileFullEe | 3 +++ docker/RHEL8/Dockerfile | 5 ++++- docker/RHEL9/Dockerfile | 5 ++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6fa79b12b0..6ad8713931 100644 --- a/Dockerfile +++ b/Dockerfile @@ -132,6 +132,7 @@ ARG WITH_POWERSHELL=true ARG WITH_KUBECTL=true ARG WITH_HELM=true ARG WITH_GIT=true +ARG features="" # To change latest stable version: # 1. Change placeholder in instanceSettings.ts @@ -149,7 +150,8 @@ ENV PATH /usr/local/bin:/root/.local/bin:/tmp/.local/bin:$PATH RUN apt-get update \ - && apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common tini libsasl2-modules-gssapi-mit krb5-user \ + && apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common tini \ + && if echo "$features" | grep -q "ee"; then apt-get install -y --no-install-recommends libsasl2-modules-gssapi-mit krb5-user; fi \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docker/DockerfileFullEe b/docker/DockerfileFullEe index 7404f42efa..0476ee2c10 100644 --- a/docker/DockerfileFullEe +++ b/docker/DockerfileFullEe @@ -54,6 +54,9 @@ RUN apt-get install -y ruby ruby-bundler # iptables RUN apt-get install -y iptables +# Kerberos runtime +RUN apt-get install -y libsasl2-modules-gssapi-mit krb5-user + # Fix UV cache permissions for non-root user support (uid 1000, etc.) # The uv tool install ansible command populates the UV cache with root-owned files RUN chmod -R a+rw /tmp/windmill/cache/uv && \ diff --git a/docker/RHEL8/Dockerfile b/docker/RHEL8/Dockerfile index 6918ece6e0..f18d35ccf4 100644 --- a/docker/RHEL8/Dockerfile +++ b/docker/RHEL8/Dockerfile @@ -64,7 +64,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel krb5-workstation cyrus-sasl-devel cyrus-sasl-gssapi libcurl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel cyrus-sasl-devel libcurl-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json @@ -86,4 +86,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ RUN mkdir -p /usr/src/app && \ cp windmill-duckdb-ffi-internal/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ +# Runtime Kerberos packages (installed separately from build deps) +RUN yum install -y krb5-workstation cyrus-sasl-gssapi + RUN subscription-manager unregister diff --git a/docker/RHEL9/Dockerfile b/docker/RHEL9/Dockerfile index 31c474af77..a7048d1797 100644 --- a/docker/RHEL9/Dockerfile +++ b/docker/RHEL9/Dockerfile @@ -64,7 +64,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel krb5-workstation cyrus-sasl-devel cyrus-sasl-gssapi libcurl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel cyrus-sasl-devel libcurl-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json @@ -86,4 +86,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ RUN mkdir -p /usr/src/app && \ cp windmill-duckdb-ffi-internal/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ +# Runtime Kerberos packages (installed separately from build deps) +RUN yum install -y krb5-workstation cyrus-sasl-gssapi + RUN subscription-manager unregister From dd419ade94a992073dfd1a979bfeb8a5fadfb051 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Thu, 12 Feb 2026 09:50:01 +0000 Subject: [PATCH 25/93] fix(frontend): redesign instance settings (#7916) * handle dirty config * Move instance update in drawer actions * Put windmill version in drawer header * Use sidebar instead of tabs * Rework user section * improve user table * Handle EE * Add settings section header * test-1 * option 2 * create new settings group * harmonize all settings inputs * improve members/users table styling * refactor instance setup * show user count * nit * Create setting card component * harmonize instance settings and workspace settings * fix python version loader * nit * clean code * nit * add email validation * fix reactivity issue on default value * fix dirty config check * fix object storage dirty config check * Fix object storage settings reactivity * fix indexer dirty reactivity * Add validation for indexer * fix sso dirty issues * clean * nit --- .../src/lib/components/Auth0Setting.svelte | 8 +- .../src/lib/components/AuthSettings.svelte | 75 +-- .../src/lib/components/AutheliaSetting.svelte | 5 +- .../lib/components/AuthentikSetting.svelte | 5 +- .../src/lib/components/CenteredModal.svelte | 18 +- .../lib/components/ConnectionSection.svelte | 142 ++-- frontend/src/lib/components/CustomSso.svelte | 14 +- .../src/lib/components/DeployToSetting.svelte | 220 ++++--- .../src/lib/components/Description.svelte | 4 +- .../components/ErrorOrRecoveryHandler.svelte | 2 +- frontend/src/lib/components/InputError.svelte | 15 + .../lib/components/InstanceNameEditor.svelte | 4 +- .../src/lib/components/InstanceSetting.svelte | 472 +++----------- .../lib/components/InstanceSettings.svelte | 613 +++++++++++------- .../src/lib/components/IntegerInput.svelte | 86 +++ .../lib/components/InviteGlobalUser.svelte | 51 +- .../src/lib/components/KanidmSetting.svelte | 5 +- .../src/lib/components/KeycloakSetting.svelte | 5 +- .../lib/components/NextcloudSetting.svelte | 5 +- .../src/lib/components/OAuthSetting.svelte | 5 +- .../ObjectStoreConfigSettings.svelte | 224 +++++-- .../src/lib/components/OktaSetting.svelte | 5 +- .../src/lib/components/PocketIdSetting.svelte | 5 +- .../lib/components/SuperadminSettings.svelte | 58 +- .../components/SuperadminSettingsInner.svelte | 487 ++++++++------ frontend/src/lib/components/Uptodate.svelte | 5 +- .../src/lib/components/ZitadelSetting.svelte | 5 +- .../common/breadcrumb/Breadcrumb.svelte | 36 +- .../common/drawer/DrawerContent.svelte | 2 +- .../lib/components/copilot/TestAIKey.svelte | 2 +- .../git_sync/GitSyncRepositoryCard.svelte | 6 +- .../components/git_sync/GitSyncSection.svelte | 29 +- .../src/lib/components/instanceSettings.ts | 331 ++++++++-- .../IndexerJobIndexSettings.svelte | 103 +++ .../IndexerLogIndexSettings.svelte | 76 +++ .../IndexerMemorySettings.svelte | 119 ++++ .../SecretBackendConfig.svelte | 30 +- .../instanceSettings/SettingCard.svelte | 71 ++ .../settings/WorkspaceUserSettings.svelte | 6 +- .../sidebar/CriticalAlertModalInner.svelte | 2 +- .../workspaceSettings/AISettings.svelte | 31 +- .../DataTableSettings.svelte | 2 +- .../workspaceSettings/DucklakeSettings.svelte | 2 +- .../workspaceSettings/ModelTokenLimits.svelte | 16 +- .../workspaceSettings/SettingsFooter.svelte | 7 +- .../workspaceSettings/StorageSettings.svelte | 2 +- .../WorkspaceIntegrations.svelte | 6 +- .../WorkspaceRulesets.svelte | 7 +- frontend/src/lib/stores.ts | 2 +- .../user/(user)/first-time/+page.svelte | 17 +- .../(user)/instance_settings/+page.svelte | 225 ++++++- .../(logged)/workspace_settings/+page.svelte | 95 ++- 52 files changed, 2411 insertions(+), 1357 deletions(-) create mode 100644 frontend/src/lib/components/InputError.svelte create mode 100644 frontend/src/lib/components/IntegerInput.svelte create mode 100644 frontend/src/lib/components/instanceSettings/IndexerJobIndexSettings.svelte create mode 100644 frontend/src/lib/components/instanceSettings/IndexerLogIndexSettings.svelte create mode 100644 frontend/src/lib/components/instanceSettings/IndexerMemorySettings.svelte create mode 100644 frontend/src/lib/components/instanceSettings/SettingCard.svelte diff --git a/frontend/src/lib/components/Auth0Setting.svelte b/frontend/src/lib/components/Auth0Setting.svelte index 8ad744f286..beb9ec03f9 100644 --- a/frontend/src/lib/components/Auth0Setting.svelte +++ b/frontend/src/lib/components/Auth0Setting.svelte @@ -7,6 +7,7 @@ import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte' import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte' import TextInput from './text_input/TextInput.svelte' + import SettingCard from './instanceSettings/SettingCard.svelte' interface Props { value: any @@ -58,7 +59,7 @@ /> {#if enabled} -
+
+ {/if}
diff --git a/frontend/src/lib/components/AuthSettings.svelte b/frontend/src/lib/components/AuthSettings.svelte index 8d9d90ff2b..c157808017 100644 --- a/frontend/src/lib/components/AuthSettings.svelte +++ b/frontend/src/lib/components/AuthSettings.svelte @@ -23,6 +23,7 @@ import Tooltip from './Tooltip.svelte' import { tick } from 'svelte' import { Popover } from './meltComponents' + import SettingsPageHeader from './settings/SettingsPageHeader.svelte' interface Props { snowflakeAccountIdentifier?: string @@ -30,6 +31,8 @@ requirePreexistingUserForOauth?: boolean baseUrl?: string scim?: import('svelte').Snippet + tab?: 'sso' | 'oauth' | 'scim' + hideTabs?: boolean } let { @@ -37,7 +40,9 @@ oauths = $bindable(), requirePreexistingUserForOauth = $bindable(), baseUrl, - scim + scim, + tab = $bindable('sso'), + hideTabs = false }: Props = $props() $effect(() => { @@ -87,8 +92,6 @@ let ssoClientName = $state('') let ssoNameInput = $state() - let tab: 'sso' | 'oauth' | 'scim' = $state('sso') - function createOAuthClient(name: string) { if (oauths && name) { // Create a new object to ensure the new item is added at the end @@ -212,34 +215,30 @@ } -
- - - - - -
+{#if !hideTabs} +
+ + + + + +
+{/if}
{#if oauths} {#if tab === 'sso'} + {#if !$enterpriseLicense || $enterpriseLicense.endsWith('_pro')} - + Without EE, the number of SSO users is limited to 10. SCIM/SAML is available on EE
{/if} - -
-
When at least one of the below options is set, users will be able to login to Windmill - via their third-party account. -
To test SSO, the recommended workflow is to to save the settings and try to login - in an incognito window. - Learn more -
-
-
-
{/if} {:else if tab == 'scim'} + {@render scim?.()} {/if} {/if} diff --git a/frontend/src/lib/components/AutheliaSetting.svelte b/frontend/src/lib/components/AutheliaSetting.svelte index ece8cb7d58..bd32b632a9 100644 --- a/frontend/src/lib/components/AutheliaSetting.svelte +++ b/frontend/src/lib/components/AutheliaSetting.svelte @@ -2,6 +2,7 @@ import IconedResourceType from './IconedResourceType.svelte' import TextInput from './text_input/TextInput.svelte' import Toggle from './Toggle.svelte' + import SettingCard from './instanceSettings/SettingCard.svelte' export let value: any @@ -46,7 +47,7 @@ /> {#if enabled} -
+ -
+ {/if}
diff --git a/frontend/src/lib/components/AuthentikSetting.svelte b/frontend/src/lib/components/AuthentikSetting.svelte index 70d56c5abf..6aa6a76d57 100644 --- a/frontend/src/lib/components/AuthentikSetting.svelte +++ b/frontend/src/lib/components/AuthentikSetting.svelte @@ -1,6 +1,7 @@
-
1080 ? 'pt-28' : 'pt-12')} > +
1080 ? 'pt-28' : 'pt-12' + )} + > {#if (!disableLogo && !$enterpriseLicense) || !$whitelabelNameStore}
+ +
+
+

Requires a superadmin token.

+
curl -X POST '{window.location.origin}/api/configs/update/worker__{name}' \
+  -H 'Content-Type: application/json' \
+  -H 'Authorization: Bearer <token>' \
+  -d '{JSON.stringify(cleanWorkerGroupConfig(nconfig ?? {}), null, 2)}'
+
+
{#snippet actions()}
{#if onOpenYamlEditor} diff --git a/frontend/src/lib/components/worker_group.ts b/frontend/src/lib/components/worker_group.ts index 70f7741bae..0547c811c9 100644 --- a/frontend/src/lib/components/worker_group.ts +++ b/frontend/src/lib/components/worker_group.ts @@ -61,6 +61,19 @@ export const defaultTags = [ 'duckdb' // for related places search: ADD_NEW_LANG ] +/** Strip cache_clear, null/undefined values, empty arrays and empty objects from a worker group config. */ +export function cleanWorkerGroupConfig(config: Record): Record { + const { cache_clear, ...rest } = config + const cleaned: Record = {} + for (const [k, v] of Object.entries(rest)) { + if (v == null) continue + if (Array.isArray(v) && v.length === 0) continue + if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue + cleaned[k] = v + } + return cleaned +} + export const nativeTags = [ 'nativets', 'postgresql', diff --git a/frontend/src/routes/(root)/(logged)/workers/+page.svelte b/frontend/src/routes/(root)/(logged)/workers/+page.svelte index 412ac84ec2..23e8b9bb2e 100644 --- a/frontend/src/routes/(root)/(logged)/workers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/workers/+page.svelte @@ -39,6 +39,7 @@ import { onDestroy, onMount, untrack } from 'svelte' import YAML from 'yaml' + import { cleanWorkerGroupConfig } from '$lib/components/worker_group' import { DEFAULT_TAGS_WORKSPACES_SETTING } from '$lib/consts' import AutoscalingEvents from '$lib/components/AutoscalingEvents.svelte' import HttpAgentWorkerDrawer from '$lib/components/HttpAgentWorkerDrawer.svelte' @@ -397,15 +398,8 @@ if (bi !== -1) return 1 return a.localeCompare(b) }) - for (const [name, { cache_clear, ...config }] of entries) { - const cleaned: Record = {} - for (const [k, v] of Object.entries(config)) { - if (v == null) continue - if (Array.isArray(v) && v.length === 0) continue - if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue - cleaned[k] = v - } - sorted[name] = cleaned + for (const [name, config] of entries) { + sorted[name] = cleanWorkerGroupConfig(config) } return YAML.stringify(sorted) } From 0cc4e2650c5b8ef6cf0a82635d199849c4900d68 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Feb 2026 16:38:07 +0000 Subject: [PATCH 88/93] make api for setting instance config more consistent --- backend/windmill-api-settings/src/lib.rs | 29 ++++++++----------- backend/windmill-api/openapi.yaml | 8 +---- .../lib/components/InstanceSettings.svelte | 3 +- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index c52192b6c3..f498a8f572 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -407,16 +407,9 @@ async fn get_instance_config( Ok(Json(config)) } -#[derive(Deserialize)] -struct SetInstanceConfigQuery { - #[serde(default)] - skip_worker_configs: Option, -} - async fn set_instance_config( Extension(db): Extension, authed: ApiAuthed, - Query(query): Query, Json(desired): Json, ) -> error::Result<()> { require_super_admin(&db, &authed.email).await?; @@ -425,20 +418,22 @@ async fn set_instance_config( .await .map_err(|e| error::Error::internal_err(e.to_string()))?; - let current_map = current.global_settings.to_settings_map(); let desired_map = desired.global_settings.to_settings_map(); - let settings_diff = - instance_config::diff_global_settings(¤t_map, &desired_map, ApplyMode::Merge); + if !desired_map.is_empty() { + let current_map = current.global_settings.to_settings_map(); + let settings_diff = + instance_config::diff_global_settings(¤t_map, &desired_map, ApplyMode::Merge); - for (key, value) in &settings_diff.upserts { - run_setting_pre_write_hook(&db, key, value).await?; + for (key, value) in &settings_diff.upserts { + run_setting_pre_write_hook(&db, key, value).await?; + } + + instance_config::apply_settings_diff(&db, &settings_diff) + .await + .map_err(|e| error::Error::internal_err(e.to_string()))?; } - instance_config::apply_settings_diff(&db, &settings_diff) - .await - .map_err(|e| error::Error::internal_err(e.to_string()))?; - - if !query.skip_worker_configs.unwrap_or(false) { + if !desired.worker_configs.is_empty() { let current_wc: std::collections::BTreeMap = current .worker_configs .iter() diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index cbd917a542..b769a279ec 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1497,16 +1497,10 @@ paths: schema: $ref: "#/components/schemas/InstanceConfig" put: - summary: update instance config (bulk upsert, no deletes) + summary: update instance config (bulk upsert, no deletes). Empty or missing global_settings/worker_configs are skipped. operationId: setInstanceConfig tags: - setting - parameters: - - name: skip_worker_configs - in: query - description: if true, ignore worker_configs in the request body - schema: - type: boolean requestBody: description: full instance configuration to apply required: true diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index bf9d7a38b8..785c599c84 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -180,8 +180,7 @@ } await SettingService.setInstanceConfig({ - requestBody: { global_settings: globalSettings }, - skipWorkerConfigs: true + requestBody: { global_settings: globalSettings } }) initialValues = JSON.parse(JSON.stringify($values)) From 2ca3c8e4098575c53e48cee4965e5fe8fa326077 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Feb 2026 16:44:43 +0000 Subject: [PATCH 89/93] nit --- backend/windmill-api-settings/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index f498a8f572..e2768a8a0f 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -22,10 +22,12 @@ use ee_oss::validate_license_key; use windmill_common::usernames::generate_instance_username_for_all_users; use axum::{ - extract::{Extension, Path, Query}, + extract::{Extension, Path}, routing::{get, post}, Json, Router, }; +#[cfg(feature = "enterprise")] +use axum::extract::Query; use serde_json::json; use serde::{Deserialize, Serialize}; From d9a5cb64b8d3f14f6eb26e1fc6e97faa3ec63112 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Feb 2026 17:48:38 +0100 Subject: [PATCH 90/93] chore(main): release 1.635.0 (#7945) * chore(main): release 2.0.0 * Apply automatic changes * chore(main): release 1.635.0 Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 20 ++ backend/Cargo.lock | 215 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 50 +++- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 189 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a75e458b..13d4d4fed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.635.0](https://github.com/windmill-labs/windmill/compare/v1.634.6...v1.635.0) (2026-02-15) + + +### Features + +* add Kubernetes operator and instance settings YAML editor ([#7836](https://github.com/windmill-labs/windmill/issues/7836)) ([82e5f6d](https://github.com/windmill-labs/windmill/commit/82e5f6de48e246a49b25e7d4ea7be65122e8772c)) +* add maven settings.xml support for java private registries ([#7940](https://github.com/windmill-labs/windmill/issues/7940)) ([581dde8](https://github.com/windmill-labs/windmill/commit/581dde8d0bc4428a5e95fcb5341239231ab36ef6)) +* **cli:** add `lint` command ([#7917](https://github.com/windmill-labs/windmill/issues/7917)) ([37d1277](https://github.com/windmill-labs/windmill/commit/37d1277b91d1b8a03e327b0585f547037482498d)) +* handle $var: and $res: in arrays for transform_json_value ([#7949](https://github.com/windmill-labs/windmill/issues/7949)) ([e4a34d0](https://github.com/windmill-labs/windmill/commit/e4a34d031b2bdb1b73a2a7ca68544fa34f83ed0f)) +* IaC hints, YAML editor for worker configs ([#7956](https://github.com/windmill-labs/windmill/issues/7956)) ([8b8e33e](https://github.com/windmill-labs/windmill/commit/8b8e33e2dc1a2b4c0effab70463f6d4b402a0f7f)) +* open-source worker group configuration UI ([#7954](https://github.com/windmill-labs/windmill/issues/7954)) ([6cf3f5f](https://github.com/windmill-labs/windmill/commit/6cf3f5f4a35a6139b5cdf9f44af29c3941f19645)) + + +### Bug Fixes + +* allow renaming of backend runnables in the UI ([6215760](https://github.com/windmill-labs/windmill/commit/6215760b1294d55245909a1c1de6c4cc8cef320a)) +* **go:** preserve proxy envs for go mod tidy/download ([#7946](https://github.com/windmill-labs/windmill/issues/7946)) ([8410b59](https://github.com/windmill-labs/windmill/commit/8410b59a8f23d62c57e497d170449643b46595a0)) +* Missing app policy for datatable ([#7944](https://github.com/windmill-labs/windmill/issues/7944)) ([a9dbd1f](https://github.com/windmill-labs/windmill/commit/a9dbd1f73fca9100b64106281802c43881181e78)) +* strip slack_oauth_client_secret from get_settings for non-admins ([#7950](https://github.com/windmill-labs/windmill/issues/7950)) ([43218c6](https://github.com/windmill-labs/windmill/commit/43218c62852490d0efafa8f94385bfe0e8f2ad82)) + ## [1.634.6](https://github.com/windmill-labs/windmill/compare/v1.634.5...v1.634.6) (2026-02-13) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 6420b5e4de..a4baabda48 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -2119,7 +2119,7 @@ dependencies = [ "gemm", "half", "libm", - "memmap2 0.9.9", + "memmap2 0.9.10", "num-traits", "num_cpus", "rand 0.9.0", @@ -5841,9 +5841,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -5856,9 +5856,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -5866,15 +5866,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -5894,9 +5894,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -5913,9 +5913,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -5924,21 +5924,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -5948,7 +5948,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -7673,9 +7672,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -8458,9 +8457,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", "stable_deref_trait", @@ -8795,17 +8794,17 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cdede44f9a69cab2899a2049e2c3bd49bf911a157f6a3353d4a91c61abbce44" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe 0.2.1", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework 3.6.0", "security-framework-sys", "tempfile", ] @@ -11686,7 +11685,7 @@ dependencies = [ "openssl-probe 0.2.1", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework 3.6.0", ] [[package]] @@ -11732,7 +11731,7 @@ dependencies = [ "rustls-native-certs 0.8.3", "rustls-platform-verifier-android", "rustls-webpki 0.103.9", - "security-framework 3.5.1", + "security-framework 3.6.0", "security-framework-sys", "webpki-root-certs 1.0.6", "windows-sys 0.61.2", @@ -12120,9 +12119,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags 2.9.4", "core-foundation 0.10.1", @@ -12133,9 +12132,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -13708,7 +13707,7 @@ dependencies = [ "lru 0.12.5", "lz4_flex", "measure_time", - "memmap2 0.9.9", + "memmap2 0.9.10", "once_cell", "oneshot", "rayon", @@ -15726,7 +15725,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.634.6" +version = "1.635.0" dependencies = [ "anyhow", "async-nats", @@ -15789,7 +15788,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15802,7 +15801,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "argon2", @@ -15939,7 +15938,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15962,7 +15961,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15975,7 +15974,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16001,7 +16000,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.634.6" +version = "2.0.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16011,7 +16010,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16028,7 +16027,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16051,7 +16050,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16074,7 +16073,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16090,7 +16089,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16110,7 +16109,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16130,7 +16129,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16144,7 +16143,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-nats", @@ -16168,7 +16167,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16193,7 +16192,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "flate2", @@ -16209,7 +16208,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16230,7 +16229,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16250,7 +16249,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16279,7 +16278,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16305,7 +16304,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.634.6" +version = "2.0.0" dependencies = [ "lazy_static", "serde", @@ -16317,7 +16316,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.634.6" +version = "2.0.0" dependencies = [ "argon2", "axum 0.7.9", @@ -16340,7 +16339,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16354,7 +16353,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.634.6" +version = "2.0.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16383,7 +16382,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.634.6" +version = "2.0.0" dependencies = [ "chrono", "lazy_static", @@ -16397,7 +16396,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16416,7 +16415,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.634.6" +version = "2.0.0" dependencies = [ "aes-gcm", "anyhow", @@ -16516,7 +16515,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.634.6" +version = "2.0.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16535,7 +16534,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.634.6" +version = "2.0.0" dependencies = [ "regex", "serde", @@ -16550,7 +16549,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16574,7 +16573,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "futures", @@ -16591,7 +16590,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.634.6" +version = "2.0.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16607,7 +16606,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -16628,7 +16627,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -16659,7 +16658,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-oauth2", @@ -16683,7 +16682,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "chrono", @@ -16703,7 +16702,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.634.6" +version = "2.0.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16712,7 +16711,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "lazy_static", @@ -16724,7 +16723,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "serde_json", @@ -16736,7 +16735,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "gosyn", @@ -16748,7 +16747,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "lazy_static", @@ -16760,7 +16759,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "serde_json", @@ -16772,7 +16771,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "nu-parser", @@ -16783,7 +16782,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16794,7 +16793,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16807,7 +16806,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-recursion", @@ -16831,7 +16830,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "lazy_static", @@ -16845,7 +16844,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16862,7 +16861,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "lazy_static", @@ -16876,7 +16875,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "lazy_static", @@ -16895,7 +16894,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "serde", @@ -16906,7 +16905,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-recursion", @@ -16943,7 +16942,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "const_format", @@ -16981,7 +16980,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.634.6" +version = "2.0.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -16991,7 +16990,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-recursion", @@ -17020,7 +17019,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -17043,7 +17042,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17076,7 +17075,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17096,7 +17095,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17130,7 +17129,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17164,7 +17163,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17187,7 +17186,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17211,7 +17210,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-nats", @@ -17235,7 +17234,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17270,7 +17269,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17298,7 +17297,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", @@ -17321,7 +17320,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "chrono", @@ -17338,7 +17337,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.634.6" +version = "2.0.0" dependencies = [ "anyhow", "async-once-cell", @@ -18401,9 +18400,9 @@ dependencies = [ [[package]] name = "zip" -version = "7.4.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc12baa6db2b15a140161ce53d72209dacea594230798c24774139b54ecaa980" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ "crc32fast", "indexmap 2.11.1", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index bf3a18c486..e9ea8735d1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.634.6" +version = "1.635.0" authors.workspace = true edition.workspace = true @@ -75,7 +75,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.634.6" +version = "1.635.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index b769a279ec..74438f6cb9 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.634.6 + version: 1.635.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index a857c457bf..998aeae3c2 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.634.6"; +export const VERSION = "v1.635.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 7a81e9b288..434b3177e5 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -79,7 +79,7 @@ export { // } // }); -export const VERSION = "1.634.6"; +export const VERSION = "1.635.0"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 023cef2eed..5d315c35e6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.634.6", + "version": "1.635.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.634.6", + "version": "1.635.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -834,6 +834,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -845,6 +846,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -855,6 +857,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1344,6 +1347,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1498,6 +1502,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1514,6 +1519,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1530,6 +1536,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1546,6 +1553,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1562,6 +1570,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1578,6 +1587,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1594,6 +1604,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1610,6 +1621,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1626,6 +1638,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1642,6 +1655,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1658,6 +1672,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1674,6 +1689,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1690,6 +1706,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2310,6 +2327,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -7058,7 +7076,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -7557,6 +7575,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7577,6 +7596,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7597,6 +7617,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7617,6 +7638,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7637,6 +7659,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7657,6 +7680,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7677,6 +7701,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7697,6 +7722,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7717,6 +7743,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7737,6 +7764,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7757,6 +7785,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -12383,6 +12412,21 @@ } } }, + "node_modules/svelte-check/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index c080507d56..9cd2848cd2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.634.6", + "version": "1.635.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 89b39f44c3..f64eea07f3 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.634.6" -wmill_pg = ">=1.634.6" +wmill = ">=1.635.0" +wmill_pg = ">=1.635.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 3c983a8bcf..414a8b46b0 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.634.6 + version: 1.635.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 6b99b77f6a..ab0d301459 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.634.6' + ModuleVersion = '1.635.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index e1c0812033..c7a29547e6 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.634.6" +version = "1.635.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index d18ed4587c..bf4ae4b66e 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.634.6" +version = "1.635.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 594f84eaed..73e7464f06 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.634.6", + "version": "1.635.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 00dda96a47..ce3139809c 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.634.6", + "version": "1.635.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index d760354d4b..3965a00757 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.634.6 +1.635.0 From 68f766e1ae54dbe2fe42769559d81d4d76a409ef Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Feb 2026 17:56:39 +0100 Subject: [PATCH 91/93] fix: pin strum_macros to 0.27 to match strum version (#7957) Co-authored-by: Claude Opus 4.6 --- backend/Cargo.lock | 138 ++++++++++++++++++++++----------------------- backend/Cargo.toml | 2 +- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index a4baabda48..4634554c14 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15788,7 +15788,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15801,7 +15801,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "argon2", @@ -15876,7 +15876,7 @@ dependencies = [ "tokio-postgres 0.7.13", "tokio-stream", "tokio-util", - "tower 0.5.3", + "tower 0.4.13", "tower-cookies", "tower-http", "tracing", @@ -15938,7 +15938,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15961,7 +15961,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15974,7 +15974,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16000,7 +16000,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "2.0.0" +version = "1.635.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16010,7 +16010,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16027,7 +16027,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16050,7 +16050,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16073,7 +16073,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16089,7 +16089,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16109,7 +16109,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16129,7 +16129,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16143,7 +16143,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-nats", @@ -16167,7 +16167,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16192,7 +16192,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "flate2", @@ -16208,7 +16208,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16229,7 +16229,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16249,7 +16249,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16278,7 +16278,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16304,7 +16304,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "2.0.0" +version = "1.635.0" dependencies = [ "lazy_static", "serde", @@ -16316,7 +16316,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "2.0.0" +version = "1.635.0" dependencies = [ "argon2", "axum 0.7.9", @@ -16339,7 +16339,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16353,7 +16353,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "2.0.0" +version = "1.635.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16382,7 +16382,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "2.0.0" +version = "1.635.0" dependencies = [ "chrono", "lazy_static", @@ -16396,7 +16396,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16415,7 +16415,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "2.0.0" +version = "1.635.0" dependencies = [ "aes-gcm", "anyhow", @@ -16515,7 +16515,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "2.0.0" +version = "1.635.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16534,7 +16534,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "2.0.0" +version = "1.635.0" dependencies = [ "regex", "serde", @@ -16549,7 +16549,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16573,7 +16573,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "futures", @@ -16590,7 +16590,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "2.0.0" +version = "1.635.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16606,7 +16606,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -16627,7 +16627,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -16658,7 +16658,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-oauth2", @@ -16682,7 +16682,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "chrono", @@ -16702,7 +16702,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "2.0.0" +version = "1.635.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16711,7 +16711,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "lazy_static", @@ -16723,7 +16723,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "serde_json", @@ -16735,7 +16735,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "gosyn", @@ -16747,7 +16747,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "lazy_static", @@ -16759,7 +16759,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "serde_json", @@ -16771,7 +16771,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "nu-parser", @@ -16782,7 +16782,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16793,7 +16793,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16806,7 +16806,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-recursion", @@ -16830,7 +16830,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "lazy_static", @@ -16844,7 +16844,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16861,7 +16861,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "lazy_static", @@ -16875,7 +16875,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "lazy_static", @@ -16894,7 +16894,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "serde", @@ -16905,7 +16905,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-recursion", @@ -16942,7 +16942,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "const_format", @@ -16980,7 +16980,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "2.0.0" +version = "1.635.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -16990,7 +16990,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-recursion", @@ -17019,7 +17019,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -17042,7 +17042,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17075,7 +17075,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17095,7 +17095,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17129,7 +17129,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17163,7 +17163,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17186,7 +17186,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17210,7 +17210,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-nats", @@ -17234,7 +17234,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17269,7 +17269,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17297,7 +17297,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-trait", @@ -17320,7 +17320,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "chrono", @@ -17337,7 +17337,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "2.0.0" +version = "1.635.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e9ea8735d1..0d82d63922 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -584,7 +584,7 @@ tree-sitter-ruby = "0.23.0" oracle = { version = "0.6.3", features = ["chrono"] } rumqttc = { version = "0.24.0", features = ["use-native-tls"]} strum = { version = "0.27", features = ["derive"] } -strum_macros = "^0" +strum_macros = "0.27" hudsucker = { version = "0.22", features = ["rcgen-ca", "native-tls-client"] } hyper-http-proxy = { version = "1", default-features = false, features = ["native-tls"] } rcgen = "0.13" From 714f713a04c02a88a07c90f1c979aa2e14a7ca2c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Feb 2026 18:13:37 +0100 Subject: [PATCH 92/93] only update license key via IaC when new expiry is posterior (#7959) When using infrastructure-as-code to update settings, if the desired license key matches the current one (same client ID and signature) but differs only in the expiration date, only apply the update if the new key has a later expiry. This prevents accidental downgrades when an older license key is present in the IaC configuration. Co-authored-by: Claude Opus 4.6 --- .../windmill-common/src/instance_config.rs | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index 609d76b138..1b39ea0325 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -3,6 +3,8 @@ use std::fmt; use serde::{Deserialize, Serialize}; +use crate::global_settings::LICENSE_KEY_SETTING; + // --------------------------------------------------------------------------- // Kubernetes Secret reference support // --------------------------------------------------------------------------- @@ -746,6 +748,33 @@ pub const PROTECTED_SETTINGS: &[&str] = &[ /// Internal settings that are never exposed via the API or included in config exports. pub const HIDDEN_SETTINGS: &[&str] = &["uid", "rsa_keys", "jwt_secret", "min_keep_alive_version"]; +/// Extract the expiry timestamp from a license key JSON value. +/// +/// License keys have the format `..`. +/// Returns `None` if the value is not a string or doesn't match the format. +fn license_key_expiry(value: &serde_json::Value) -> Option { + let s = value.as_str()?; + let parts: Vec<&str> = s.split('.').collect(); + if parts.len() != 3 { + return None; + } + parts[1].parse::().ok() +} + +/// Returns true if two license key values share the same client ID and signature +/// (i.e. they differ only in the expiry field). +fn license_keys_same_except_expiry(a: &serde_json::Value, b: &serde_json::Value) -> bool { + let (Some(a_str), Some(b_str)) = (a.as_str(), b.as_str()) else { + return false; + }; + let a_parts: Vec<&str> = a_str.split('.').collect(); + let b_parts: Vec<&str> = b_str.split('.').collect(); + if a_parts.len() != 3 || b_parts.len() != 3 { + return false; + } + a_parts[0] == b_parts[0] && a_parts[2] == b_parts[2] +} + /// Compute the diff between current and desired global settings. pub fn diff_global_settings( current: &BTreeMap, @@ -756,6 +785,23 @@ pub fn diff_global_settings( for (key, value) in desired { match current.get(key) { Some(existing) if existing == value => {} // no change + Some(existing) if key == LICENSE_KEY_SETTING => { + if license_keys_same_except_expiry(existing, value) { + let current_expiry = license_key_expiry(existing).unwrap_or(0); + let desired_expiry = license_key_expiry(value).unwrap_or(0); + if desired_expiry > current_expiry { + upserts.insert(key.clone(), value.clone()); + } else { + tracing::info!( + "Skipping license_key update: desired expiry ({}) is not posterior to current expiry ({})", + desired_expiry, + current_expiry + ); + } + } else { + upserts.insert(key.clone(), value.clone()); + } + } _ => { upserts.insert(key.clone(), value.clone()); } @@ -1939,4 +1985,117 @@ mod tests { Some("plain-token") ); } + + // ----------------------------------------------------------------------- + // License key expiry diff tests + // ----------------------------------------------------------------------- + + #[test] + fn diff_license_key_skips_older_expiry() { + let mut current = BTreeMap::new(); + current.insert( + "license_key".to_string(), + serde_json::json!("client1.2000000000.sig123"), + ); + + let mut desired = BTreeMap::new(); + desired.insert( + "license_key".to_string(), + serde_json::json!("client1.1000000000.sig123"), + ); + + let diff = diff_global_settings(¤t, &desired, ApplyMode::Merge); + assert!( + diff.upserts.is_empty(), + "Should not update license_key when desired expiry is older" + ); + } + + #[test] + fn diff_license_key_skips_equal_expiry() { + let mut current = BTreeMap::new(); + current.insert( + "license_key".to_string(), + serde_json::json!("client1.2000000000.sig_a"), + ); + + let mut desired = BTreeMap::new(); + desired.insert( + "license_key".to_string(), + serde_json::json!("client1.2000000000.sig_b"), + ); + + let diff = diff_global_settings(¤t, &desired, ApplyMode::Merge); + assert_eq!( + diff.upserts.len(), + 1, + "Different signature means different key, should update" + ); + } + + #[test] + fn diff_license_key_updates_newer_expiry() { + let mut current = BTreeMap::new(); + current.insert( + "license_key".to_string(), + serde_json::json!("client1.1000000000.sig123"), + ); + + let mut desired = BTreeMap::new(); + desired.insert( + "license_key".to_string(), + serde_json::json!("client1.2000000000.sig123"), + ); + + let diff = diff_global_settings(¤t, &desired, ApplyMode::Merge); + assert_eq!(diff.upserts.len(), 1); + assert_eq!( + diff.upserts["license_key"], + serde_json::json!("client1.2000000000.sig123") + ); + } + + #[test] + fn diff_license_key_updates_different_client() { + let mut current = BTreeMap::new(); + current.insert( + "license_key".to_string(), + serde_json::json!("client1.2000000000.sig123"), + ); + + let mut desired = BTreeMap::new(); + desired.insert( + "license_key".to_string(), + serde_json::json!("client2.1000000000.sig456"), + ); + + let diff = diff_global_settings(¤t, &desired, ApplyMode::Merge); + assert_eq!( + diff.upserts.len(), + 1, + "Different client ID means different key, should always update" + ); + } + + #[test] + fn diff_license_key_non_string_always_updates() { + let mut current = BTreeMap::new(); + current.insert( + "license_key".to_string(), + serde_json::json!({"envRef": "LIC_KEY"}), + ); + + let mut desired = BTreeMap::new(); + desired.insert( + "license_key".to_string(), + serde_json::json!({"envRef": "NEW_LIC_KEY"}), + ); + + let diff = diff_global_settings(¤t, &desired, ApplyMode::Merge); + assert_eq!( + diff.upserts.len(), + 1, + "Non-string license keys should always be updated when different" + ); + } } From ba80570357d1360571721556420f17cfd6af3992 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Feb 2026 18:14:04 +0100 Subject: [PATCH 93/93] chore(main): release 1.635.1 (#7958) * chore(main): release 1.635.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 + backend/Cargo.lock | 140 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 94 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d4d4fed7..e8c4b0e5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.635.1](https://github.com/windmill-labs/windmill/compare/v1.635.0...v1.635.1) (2026-02-15) + + +### Bug Fixes + +* pin strum_macros to 0.27 to match strum version ([#7957](https://github.com/windmill-labs/windmill/issues/7957)) ([68f766e](https://github.com/windmill-labs/windmill/commit/68f766e1ae54dbe2fe42769559d81d4d76a409ef)) + ## [1.635.0](https://github.com/windmill-labs/windmill/compare/v1.634.6...v1.635.0) (2026-02-15) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 4634554c14..5a3ed6ebd7 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15725,7 +15725,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-nats", @@ -15788,7 +15788,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15801,7 +15801,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "argon2", @@ -15876,7 +15876,7 @@ dependencies = [ "tokio-postgres 0.7.13", "tokio-stream", "tokio-util", - "tower 0.4.13", + "tower 0.5.3", "tower-cookies", "tower-http", "tracing", @@ -15938,7 +15938,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15961,7 +15961,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15974,7 +15974,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16000,7 +16000,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.635.0" +version = "1.635.1" dependencies = [ "reqwest 0.12.28", "serde", @@ -16010,7 +16010,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16027,7 +16027,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16050,7 +16050,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16073,7 +16073,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16089,7 +16089,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16109,7 +16109,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16129,7 +16129,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16143,7 +16143,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-nats", @@ -16167,7 +16167,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16192,7 +16192,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "flate2", @@ -16208,7 +16208,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16229,7 +16229,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16249,7 +16249,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16278,7 +16278,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16304,7 +16304,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.635.0" +version = "1.635.1" dependencies = [ "lazy_static", "serde", @@ -16316,7 +16316,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.635.0" +version = "1.635.1" dependencies = [ "argon2", "axum 0.7.9", @@ -16339,7 +16339,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16353,7 +16353,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.635.0" +version = "1.635.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16382,7 +16382,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.635.0" +version = "1.635.1" dependencies = [ "chrono", "lazy_static", @@ -16396,7 +16396,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16415,7 +16415,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.635.0" +version = "1.635.1" dependencies = [ "aes-gcm", "anyhow", @@ -16515,7 +16515,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.635.0" +version = "1.635.1" dependencies = [ "chrono", "itertools 0.14.0", @@ -16534,7 +16534,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.635.0" +version = "1.635.1" dependencies = [ "regex", "serde", @@ -16549,7 +16549,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16573,7 +16573,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "futures", @@ -16590,7 +16590,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.635.0" +version = "1.635.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16606,7 +16606,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -16627,7 +16627,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -16658,7 +16658,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-oauth2", @@ -16682,7 +16682,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "chrono", @@ -16702,7 +16702,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.635.0" +version = "1.635.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -16711,7 +16711,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "lazy_static", @@ -16723,7 +16723,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "serde_json", @@ -16735,7 +16735,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "gosyn", @@ -16747,7 +16747,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "lazy_static", @@ -16759,7 +16759,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "serde_json", @@ -16771,7 +16771,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "nu-parser", @@ -16782,7 +16782,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16793,7 +16793,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16806,7 +16806,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-recursion", @@ -16830,7 +16830,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "lazy_static", @@ -16844,7 +16844,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16861,7 +16861,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "lazy_static", @@ -16875,7 +16875,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "lazy_static", @@ -16894,7 +16894,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "serde", @@ -16905,7 +16905,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-recursion", @@ -16942,7 +16942,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "const_format", @@ -16980,7 +16980,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.635.0" +version = "1.635.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -16990,7 +16990,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-recursion", @@ -17019,7 +17019,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -17042,7 +17042,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17075,7 +17075,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17095,7 +17095,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17129,7 +17129,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17163,7 +17163,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17186,7 +17186,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17210,7 +17210,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-nats", @@ -17234,7 +17234,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17269,7 +17269,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17297,7 +17297,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-trait", @@ -17320,7 +17320,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "chrono", @@ -17337,7 +17337,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.635.0" +version = "1.635.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0d82d63922..c41289aa8c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.635.0" +version = "1.635.1" authors.workspace = true edition.workspace = true @@ -75,7 +75,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.635.0" +version = "1.635.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 74438f6cb9..138ab2586d 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.635.0 + version: 1.635.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 998aeae3c2..f6ead5a98a 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.635.0"; +export const VERSION = "v1.635.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 434b3177e5..ea65f39a47 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -79,7 +79,7 @@ export { // } // }); -export const VERSION = "1.635.0"; +export const VERSION = "1.635.1"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5d315c35e6..1e280137ad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.635.0", + "version": "1.635.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.635.0", + "version": "1.635.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 9cd2848cd2..4aea2c71c9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.635.0", + "version": "1.635.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index f64eea07f3..0bca25a2b2 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.635.0" -wmill_pg = ">=1.635.0" +wmill = ">=1.635.1" +wmill_pg = ">=1.635.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 414a8b46b0..45c66f716c 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.635.0 + version: 1.635.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index ab0d301459..20855f8c72 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.635.0' + ModuleVersion = '1.635.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index c7a29547e6..bf64982636 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.635.0" +version = "1.635.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index bf4ae4b66e..a2d60f6e6d 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.635.0" +version = "1.635.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 73e7464f06..6429bc2a25 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.635.0", + "version": "1.635.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index ce3139809c..97f3ca53c7 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.635.0", + "version": "1.635.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 3965a00757..ba455e0a89 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.635.0 +1.635.1