Merge origin/main into restricted-job-tokens

This commit is contained in:
Ruben Fiszel
2026-10-02 18:50:59 +02:00
35 changed files with 680 additions and 278 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "1.820.0"
".": "1.821.0"
}
+40
View File
@@ -1,5 +1,45 @@
# Changelog
## [1.821.0](https://github.com/windmill-labs/windmill/compare/v1.820.0...v1.821.0) (2026-10-01)
### Features
* add //no_network annotation for native scripts ([#11445](https://github.com/windmill-labs/windmill/issues/11445)) ([c25bb1e](https://github.com/windmill-labs/windmill/commit/c25bb1ed90d88167df0c36817942427a32fc3205))
* add alert actions and reduce alert padding ([#11439](https://github.com/windmill-labs/windmill/issues/11439)) ([52c3188](https://github.com/windmill-labs/windmill/commit/52c31881fc433a03ef13f7a8cde1410da8634e19))
* add test key button to AI resource drawers ([#11466](https://github.com/windmill-labs/windmill/issues/11466)) ([26be1d7](https://github.com/windmill-labs/windmill/commit/26be1d7da8be48c2e7d7a6b95d406513d649be4e))
* add wmill datatable migrate status ([#11465](https://github.com/windmill-labs/windmill/issues/11465)) ([7f1bead](https://github.com/windmill-labs/windmill/commit/7f1beadf793e8db05333d253f464954510961a5b))
* agents as a standalone kind with home listing, detail and editor pages ([#11332](https://github.com/windmill-labs/windmill/issues/11332)) ([8c7dcbb](https://github.com/windmill-labs/windmill/commit/8c7dcbbda58c86743bf3822c422be7faf1e8df51))
* dispatch ai agent tools through the worker queue ([#11227](https://github.com/windmill-labs/windmill/issues/11227)) ([039f49e](https://github.com/windmill-labs/windmill/commit/039f49ef124eacefc0ee34dcb011ea5773383845))
* group consecutive tool calls in the AI chat ([#11329](https://github.com/windmill-labs/windmill/issues/11329)) ([9613549](https://github.com/windmill-labs/windmill/commit/9613549abc1c1d63abb252e8a675c6a73142b26f))
* harden job OIDC tokens with version claims, jti and a lifetime cap ([#11457](https://github.com/windmill-labs/windmill/issues/11457)) ([227e934](https://github.com/windmill-labs/windmill/commit/227e9342108bf7c5be5721a777ff9ec544a44f08))
* labelled scope checkboxes in the oauth connect dialog ([#11460](https://github.com/windmill-labs/windmill/issues/11460)) ([840567f](https://github.com/windmill-labs/windmill/commit/840567fbe5c8779ac163b02a6e02e3d8ec1178a8))
* let operators compose flows when the workspace grants the right ([#11228](https://github.com/windmill-labs/windmill/issues/11228)) ([ca44043](https://github.com/windmill-labs/windmill/commit/ca44043e12d4713d67ca13eb546eef9d00cc965e))
* let slack connects issue a user token via user_scope ([#11452](https://github.com/windmill-labs/windmill/issues/11452)) ([d518aa5](https://github.com/windmill-labs/windmill/commit/d518aa5557910d10ec5a8ca6fff3fd0ad2fa31fc))
* make hub integrations usable as examples in global AI chat ([#10599](https://github.com/windmill-labs/windmill/issues/10599)) ([8953ca6](https://github.com/windmill-labs/windmill/commit/8953ca670aac63feab1a04369df50a669c1f40bc))
* offer slack write scopes as bot and user scope options ([#11472](https://github.com/windmill-labs/windmill/issues/11472)) ([ab2de83](https://github.com/windmill-labs/windmill/commit/ab2de838d76e2229da0867c93c2f18dc34d0d076))
* open chat path pill actions from a hover menu ([#11441](https://github.com/windmill-labs/windmill/issues/11441)) ([083db5e](https://github.com/windmill-labs/windmill/commit/083db5e38855586491920abf678e3247d8c82a43))
* rename agent memory options to On and Legacy, explain each ([#11442](https://github.com/windmill-labs/windmill/issues/11442)) ([077709b](https://github.com/windmill-labs/windmill/commit/077709b914b8b58cbd8c99f1048c7fecc2978c6b))
* **sessions:** show an item's deployed page in the preview panel ([#11458](https://github.com/windmill-labs/windmill/issues/11458)) ([b10f83c](https://github.com/windmill-labs/windmill/commit/b10f83c763abf49118c05dc08ec2b19570e21475))
### Bug Fixes
* **apps:** hide custom component host while it is not rendered ([#11434](https://github.com/windmill-labs/windmill/issues/11434)) ([c728346](https://github.com/windmill-labs/windmill/commit/c728346a151e4a0a2d1ecaae397068871186323d))
* await async def main in python scripts ([#11437](https://github.com/windmill-labs/windmill/issues/11437)) ([94ac34a](https://github.com/windmill-labs/windmill/commit/94ac34a4891a8572ad2493bdbbbf2f4911236aff))
* block runs while a JSON input does not parse ([#11463](https://github.com/windmill-labs/windmill/issues/11463)) ([ddde41b](https://github.com/windmill-labs/windmill/commit/ddde41bc1de6f7dea32b21a61ce2a5bf3bf8bba6))
* bump git sync hub scripts to cli 1.820.1 ([#11438](https://github.com/windmill-labs/windmill/issues/11438)) ([7664001](https://github.com/windmill-labs/windmill/commit/7664001b36afc80bd8d26cb12c917c971e24e96f))
* **cli:** resolve fork specific items from the parent workspace entry ([#11436](https://github.com/windmill-labs/windmill/issues/11436)) ([1d8d4a1](https://github.com/windmill-labs/windmill/commit/1d8d4a16b4396acb70484c858058353eb1ba9d6f))
* collapse run card in ai chat when the run is declined ([#11435](https://github.com/windmill-labs/windmill/issues/11435)) ([ab2a973](https://github.com/windmill-labs/windmill/commit/ab2a973357524c2c3aa81cc4aeb716d8cfd108d1))
* embed a VERSIONINFO resource in the Windows binary ([#11433](https://github.com/windmill-labs/windmill/issues/11433)) ([9028432](https://github.com/windmill-labs/windmill/commit/9028432e8a71b9601ff65160a29e683a9cc24989))
* ignore edited_at and edited_by in flow and item diffs ([#11455](https://github.com/windmill-labs/windmill/issues/11455)) ([851f6d7](https://github.com/windmill-labs/windmill/commit/851f6d74c98e849b25d6999e3d8ee492c997d5a9))
* keep flow graph rendered between deploy and navigation ([#11454](https://github.com/windmill-labs/windmill/issues/11454)) ([c0fb2db](https://github.com/windmill-labs/windmill/commit/c0fb2db24454d17d33fffa41b59e6a4cf61db44f))
* lost suspend decrement when concurrent approvals resume a flow ([#11450](https://github.com/windmill-labs/windmill/issues/11450)) ([a8e65ab](https://github.com/windmill-labs/windmill/commit/a8e65ab1e6287805f616f2531dc6a62134134e36))
* **npm-proxy:** support _auth and username/_password basic auth from npmrc ([#11467](https://github.com/windmill-labs/windmill/issues/11467)) ([1c6758a](https://github.com/windmill-labs/windmill/commit/1c6758a65651d90088f981317c06b12a19f3b73f))
* offer user scopes in the slack scope editor and drop the generated description on type change ([#11464](https://github.com/windmill-labs/windmill/issues/11464)) ([153b02a](https://github.com/windmill-labs/windmill/commit/153b02a5de19c615e6640d52ad48b1c507402851))
* restore the save to workspace button on inline flow steps ([#11469](https://github.com/windmill-labs/windmill/issues/11469)) ([88d0cd0](https://github.com/windmill-labs/windmill/commit/88d0cd00e54de7dd9b008f6f233992cb6ae69571))
* stop running deleted script versions from worker caches ([#11456](https://github.com/windmill-labs/windmill/issues/11456)) ([d2830cb](https://github.com/windmill-labs/windmill/commit/d2830cb4a0effc369aa5dbebdb32e770a2c87e18))
## [1.820.0](https://github.com/windmill-labs/windmill/compare/v1.819.0...v1.820.0) (2026-09-29)
+161 -123
View File
File diff suppressed because it is too large Load Diff
+18 -33
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.820.0"
version = "1.821.0"
authors.workspace = true
edition.workspace = true
@@ -88,7 +88,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
[workspace.package]
version = "1.820.0"
version = "1.821.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -249,36 +249,21 @@ sqlx-sqlite = { git = "https://github.com/windmill-labs/sqlx", rev = "6bdaee94fa
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
# Use tiberius main branch for libgssapi 0.8.1 fix (https://github.com/prisma/tiberius/issues/343)
tiberius = { git = "https://github.com/prisma/tiberius", rev = "59db57960a14b422fb3a1309aa4aa47880896ff8" }
# Pin tokio-postgres / postgres-types / postgres-protocol to the
# MaterializeInc fork. windmill-trigger-postgres already pulled this
# fork in transitively for the postgres-replication crate
# (CopyBothDuplex, LogicalReplicationStream, TupleData with binary
# tuple support) which upstream rust-postgres has declined to merge
# since 2021 (PR #752 → #778, both still unmerged).
# Pin tokio-postgres / postgres-types / postgres-protocol to
# windmill-labs/rust-postgres, branch `windmill-describe-first`: the
# MaterializeInc fork (postgres-replication, which upstream has not merged)
# plus `Client::describe_typed`, which pg_executor needs before
# query_typed_raw so result columns of types the client has not seen yet
# (custom enums / domains, citext, postgis) don't deadlock a large result.
# Each pinned commit carries a `windmill-*` tag so it stays reachable when the
# branch is rebased onto MaterializeInc master.
#
# MI also carries a mitigation for the
# Client::query_typed_raw / Client::prepare deadlock on result columns
# whose Oid the client doesn't know about yet (citext, custom enums /
# domains, postgis): MI's 2025-12-11 PR #33 resized the per-request
# response channel from mpsc::channel(1) → mpsc::channel(1024).
# bounded(1024) is sufficient for any realistic typeinfo deferral
# (need ~2-3 batches) but leaves a theoretical failure mode at
# >~64 MB results with a custom-Oid column. The strict-correct fix is
# mpsc::unbounded(); a follow-up PR to MI is open proposing that.
#
# The [patch.crates-io] entries below force windmill-worker's
# pg_executor (which imports `tokio_postgres::` directly from
# crates.io) onto the same fork as windmill-trigger-postgres, so the
# deadlock mitigation reaches both consumers.
#
# Upstream deadlock PRs (open, not on the critical path now that MI
# is mitigated):
# https://github.com/rust-postgres/rust-postgres/pull/1348
# https://github.com/rust-postgres/rust-postgres/pull/1349
# Reproducer: https://github.com/rubenfiszel/tokio-postgres-deadlock-repro
tokio-postgres = { git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
postgres-types = { git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
postgres-protocol = { git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
# The [patch.crates-io] entries force windmill-worker's pg_executor (which
# imports `tokio_postgres::` from crates.io) onto the same fork as
# windmill-trigger-postgres.
tokio-postgres = { git = "https://github.com/windmill-labs/rust-postgres", rev = "7f4374f9237237759305942727e136742b1e7f8b" }
postgres-types = { git = "https://github.com/windmill-labs/rust-postgres", rev = "7f4374f9237237759305942727e136742b1e7f8b" }
postgres-protocol = { git = "https://github.com/windmill-labs/rust-postgres", rev = "7f4374f9237237759305942727e136742b1e7f8b" }
[dependencies]
anyhow.workspace = true
@@ -630,8 +615,8 @@ convert_case = "0.6.0"
getrandom = "0.2"
tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]}
postgres-protocol = "0.6"
rust-postgres = { package = "tokio-postgres", git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe"}
rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/MaterializeInc/rust-postgres", features = ["runtime"], rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
rust-postgres = { package = "tokio-postgres", git = "https://github.com/windmill-labs/rust-postgres", rev = "7f4374f9237237759305942727e136742b1e7f8b"}
rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/windmill-labs/rust-postgres", features = ["runtime"], rev = "7f4374f9237237759305942727e136742b1e7f8b" }
bit-vec = "=0.6.3"
mappable-rc = "^0"
mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls", "rust_decimal"]}
+2 -1
View File
@@ -2,7 +2,8 @@
"github": {
"auth_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"scopes": ["workflow", "repo"]
"scopes": ["workflow", "repo"],
"scope_options": ["repo", "workflow", "gist", "read:org"]
},
"gitlab": {
"auth_url": "https://gitlab.com/oauth/authorize",
+24 -24
View File
@@ -6216,7 +6216,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill-common"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"aho-corasick",
"anyhow",
@@ -6302,7 +6302,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"proc-macro2",
"quote",
@@ -6314,7 +6314,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"convert_case",
"serde",
@@ -6323,7 +6323,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6335,7 +6335,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"serde_json",
@@ -6347,7 +6347,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"gosyn",
@@ -6359,7 +6359,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6371,7 +6371,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"serde_json",
@@ -6383,7 +6383,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -6394,7 +6394,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -6405,7 +6405,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -6417,7 +6417,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -6428,7 +6428,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -6450,7 +6450,7 @@ dependencies = [
[[package]]
name = "windmill-parser-r"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"serde_json",
@@ -6462,7 +6462,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6476,7 +6476,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"convert_case",
@@ -6493,7 +6493,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6506,7 +6506,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"serde",
@@ -6518,7 +6518,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6536,7 +6536,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -6552,7 +6552,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -6568,7 +6568,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"getrandom 0.2.17",
@@ -6600,7 +6600,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6614,7 +6614,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.820.0"
version = "1.821.0"
dependencies = [
"anyhow",
"bitflags",
@@ -12,7 +12,7 @@ resolver = "2"
members = ["."]
[workspace.package]
version = "1.820.0"
version = "1.821.0"
edition = "2021"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
+39 -39
View File
@@ -44,28 +44,27 @@ use windmill_common::{
CRITICAL_ALERTS_ON_TOKEN_EXPIRY_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ALERT_MUTE_ZOMBIE_JOB_RESTART_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
DISABLE_PASSWORD_LOGIN_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING,
DEPENDENCY_JOB_TAG_SETTING, DISABLE_PASSWORD_LOGIN_SETTING, EMAIL_DOMAIN_SETTING,
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING,
HTTP_ROUTE_DEFAULT_ALLOWED_ORIGINS_SETTING, HTTP_ROUTE_WORKSPACED_ROUTE_SETTING,
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
INSTANCE_EVENTS_WEBHOOK_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MAVEN_SETTINGS_XML_SETTING,
MCP_DISABLE_TOKEN_QUERY_PARAM_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
NO_DEFAULT_MAVEN_SETTING,
NPM_CONFIG_REGISTRY_SETTING, NSJAIL_TMPFS_SIZE_MB_SETTING, NSJAIL_TMP_BACKING_SETTING,
NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, OTEL_TRACES_RETENTION_SECS_SETTING,
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
POWERSHELL_REPO_URL_SETTING, PREVIEW_TAGS_OVERRIDE_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RESTART_COORDINATION_SETTING,
RETENTION_PERIOD_SECS_OVERRIDES_SETTING, RETENTION_PERIOD_SECS_SETTING, RUBY_REPOS_SETTING,
SAML_METADATA_SETTING, SANDBOX_IMAGE_CACHE_MAX_MB_SETTING,
SANDBOX_IMAGE_DEFAULT_REGISTRY_SETTING, SANDBOX_IMAGE_MAX_SIZE_MB_SETTING,
SANDBOX_IMAGE_PULL_POLICY_SETTING, SANDBOX_REGISTRY_AUTH_SETTING, SCIM_TOKEN_SETTING,
SERVICE_LOG_RETENTION_SECS_SETTING, SMTP_SETTING, STORE_AUDIT_LOGS_S3_SETTING,
TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING, UV_EXCLUDE_NEWER_SETTING,
UV_INDEX_STRATEGY_SETTING, UV_PYTHON_INSTALL_MIRROR_SETTING,
NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NSJAIL_TMPFS_SIZE_MB_SETTING,
NSJAIL_TMP_BACKING_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
OTEL_TRACES_RETENTION_SECS_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING,
POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, PREVIEW_TAGS_OVERRIDE_SETTING,
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
RESTART_COORDINATION_SETTING, RETENTION_PERIOD_SECS_OVERRIDES_SETTING,
RETENTION_PERIOD_SECS_SETTING, RUBY_REPOS_SETTING, SAML_METADATA_SETTING,
SANDBOX_IMAGE_CACHE_MAX_MB_SETTING, SANDBOX_IMAGE_DEFAULT_REGISTRY_SETTING,
SANDBOX_IMAGE_MAX_SIZE_MB_SETTING, SANDBOX_IMAGE_PULL_POLICY_SETTING,
SANDBOX_REGISTRY_AUTH_SETTING, SCIM_TOKEN_SETTING, SERVICE_LOG_RETENTION_SECS_SETTING,
SMTP_SETTING, STORE_AUDIT_LOGS_S3_SETTING, TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
UV_EXCLUDE_NEWER_SETTING, UV_INDEX_STRATEGY_SETTING, UV_PYTHON_INSTALL_MIRROR_SETTING,
WORKSPACE_FAIRNESS_DURATION_SECS_SETTING, WORKSPACE_FAIRNESS_ENABLED_SETTING,
WORKSPACE_FAIRNESS_MAX_PERCENT_SETTING, WORKSPACE_FAIRNESS_MIN_TOTAL_SETTING,
WORKSPACE_MAX_QUEUED_JOBS_SETTING, WORKSPACE_REGISTRIES_SETTING,
@@ -126,24 +125,23 @@ use windmill_worker::{
};
use crate::monitor::{
initial_load, load_concurrency_key_max_queued, load_disable_password_login,
load_fork_workspace_tag_append_fork_suffix, load_keep_job_dir,
load_mcp_disable_token_query_param, load_metrics_debug_enabled,
load_preview_tags_override, load_require_preexisting_user, load_retention_period_overrides,
load_tag_per_workspace_enabled, load_tag_per_workspace_workspaces,
load_workspace_fairness_duration_secs, load_workspace_fairness_enabled,
load_workspace_fairness_max_percent, load_workspace_fairness_min_total,
load_workspace_max_queued_jobs, monitor_db, reload_app_workspaced_route_setting,
reload_audit_log_retention_days_setting, reload_base_url_setting,
reload_bun_install_min_release_age_setting, reload_bunfig_install_scopes_setting,
reload_critical_alert_mute_ui_setting, reload_critical_alert_mute_zombie_job_restart_setting,
initial_load, load_concurrency_key_max_queued, load_dependency_job_tag,
load_disable_password_login, load_fork_workspace_tag_append_fork_suffix, load_keep_job_dir,
load_mcp_disable_token_query_param, load_metrics_debug_enabled, load_preview_tags_override,
load_require_preexisting_user, load_retention_period_overrides, load_tag_per_workspace_enabled,
load_tag_per_workspace_workspaces, load_workspace_fairness_duration_secs,
load_workspace_fairness_enabled, load_workspace_fairness_max_percent,
load_workspace_fairness_min_total, load_workspace_max_queued_jobs, monitor_db,
reload_app_workspaced_route_setting, reload_audit_log_retention_days_setting,
reload_base_url_setting, reload_bun_install_min_release_age_setting,
reload_bunfig_install_scopes_setting, reload_critical_alert_mute_ui_setting,
reload_critical_alert_mute_zombie_job_restart_setting,
reload_critical_alerts_on_token_expiry_setting, reload_critical_error_channels_setting,
reload_extra_pip_index_url_setting, reload_http_route_default_allowed_origins_setting,
reload_http_route_workspaced_route_setting,
reload_hub_api_secret_setting, reload_hub_base_url_setting,
reload_instance_events_webhook_setting, reload_job_default_timeout_setting,
reload_job_isolation_setting, reload_jwt_secret_setting, reload_license_key,
reload_npm_config_registry_setting, reload_nsjail_tmp_backing_setting,
reload_http_route_workspaced_route_setting, reload_hub_api_secret_setting,
reload_hub_base_url_setting, reload_instance_events_webhook_setting,
reload_job_default_timeout_setting, reload_job_isolation_setting, reload_jwt_secret_setting,
reload_license_key, reload_npm_config_registry_setting, reload_nsjail_tmp_backing_setting,
reload_nsjail_tmpfs_size_setting, reload_otel_traces_retention_secs_setting,
reload_otel_tracing_proxy_setting, reload_pip_index_url_setting,
reload_retention_period_setting, reload_sandbox_image_cache_max_setting,
@@ -423,7 +421,6 @@ struct HubResourceTypeRaw {
pub display_name: Option<Option<String>>,
}
/// Processed resource type with parsed schema
#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct HubResourceType {
@@ -1889,12 +1886,10 @@ async fn process_notify_event(
);
windmill_common::workspaces::PUBLIC_APP_RATE_LIMIT_CACHE.remove(payload);
}
windmill_common::SCRIPT_VERSION_DELETED_CHANNEL => {
match serde_json::from_str(payload) {
Ok(deleted) => windmill_api_scripts::scripts::evict_deleted_script_versions(deleted),
Err(e) => tracing::error!("Invalid script version deletion payload {payload}: {e}"),
}
}
windmill_common::SCRIPT_VERSION_DELETED_CHANNEL => match serde_json::from_str(payload) {
Ok(deleted) => windmill_api_scripts::scripts::evict_deleted_script_versions(deleted),
Err(e) => tracing::error!("Invalid script version deletion payload {payload}: {e}"),
},
"notify_runnable_version_change" => {
tracing::info!("Runnable version change detected: {}", payload);
match payload.split(':').collect::<Vec<&str>>().as_slice() {
@@ -2055,6 +2050,11 @@ async fn process_notify_event(
tracing::error!("Error loading preview tags override: {e:#}");
}
}
DEPENDENCY_JOB_TAG_SETTING => {
if let Err(e) = load_dependency_job_tag(db).await {
tracing::error!("Error loading dependency job tag: {e:#}");
}
}
WORKSPACE_FAIRNESS_ENABLED_SETTING => {
if let Err(e) = load_workspace_fairness_enabled(db).await {
tracing::error!("Error loading workspace fairness enabled: {e:#}");
+37 -16
View File
@@ -60,17 +60,17 @@ use windmill_common::{
CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, CRITICAL_ALERTS_ON_TOKEN_EXPIRY_SETTING,
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ALERT_MUTE_ZOMBIE_JOB_RESTART_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, DISABLE_PASSWORD_LOGIN, DISABLE_PASSWORD_LOGIN_SETTING,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING, HUB_API_SECRET_SETTING,
HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JOB_ISOLATION_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
MCP_DISABLE_TOKEN_QUERY_PARAM, MCP_DISABLE_TOKEN_QUERY_PARAM_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPMRC_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NSJAIL_TMPFS_SIZE_MB_SETTING, NSJAIL_TMP_BACKING_SETTING, NUGET_CONFIG_SETTING,
OTEL_SETTING, OTEL_TRACES_RETENTION_SECS_SETTING, OTEL_TRACING_PROXY_SETTING,
PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING,
PREVIEW_TAGS_OVERRIDE_SETTING, REQUEST_SIZE_LIMIT_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, DEPENDENCY_JOB_TAG_SETTING, DISABLE_PASSWORD_LOGIN,
DISABLE_PASSWORD_LOGIN_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING,
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MCP_DISABLE_TOKEN_QUERY_PARAM,
MCP_DISABLE_TOKEN_QUERY_PARAM_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPMRC_SETTING,
NPM_CONFIG_REGISTRY_SETTING, NSJAIL_TMPFS_SIZE_MB_SETTING, NSJAIL_TMP_BACKING_SETTING,
NUGET_CONFIG_SETTING, OTEL_SETTING, OTEL_TRACES_RETENTION_SECS_SETTING,
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
POWERSHELL_REPO_URL_SETTING, PREVIEW_TAGS_OVERRIDE_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SANDBOX_IMAGE_CACHE_MAX_MB_SETTING,
SANDBOX_IMAGE_DEFAULT_REGISTRY_SETTING, SANDBOX_IMAGE_MAX_SIZE_MB_SETTING,
@@ -93,11 +93,11 @@ use windmill_common::{
load_periodic_bash_script_interval_from_env, load_whitelist_env_vars_from_env,
load_worker_config, store_pull_query, store_suspended_pull_query, Connection, WorkerConfig,
CLOUD_HOSTED, CONCURRENCY_KEY_MAX_QUEUED, CONCURRENCY_KEY_MAX_QUEUED_DEFAULT,
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX,
INDEXER_CONFIG, PREVIEW_TAGS_OVERRIDE, SMTP_CONFIG, WINDMILL_DIR, WORKER_CONFIG,
WORKER_GROUP, WORKSPACE_FAIRNESS_DURATION_SECS, WORKSPACE_FAIRNESS_ENABLED,
WORKSPACE_FAIRNESS_MAX_PERCENT, WORKSPACE_FAIRNESS_MIN_TOTAL, WORKSPACE_MAX_QUEUED_JOBS,
WORKSPACE_MAX_QUEUED_JOBS_DEFAULT,
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, DEPENDENCY_JOB_TAG,
FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX, INDEXER_CONFIG, PREVIEW_TAGS_OVERRIDE, SMTP_CONFIG,
WINDMILL_DIR, WORKER_CONFIG, WORKER_GROUP, WORKSPACE_FAIRNESS_DURATION_SECS,
WORKSPACE_FAIRNESS_ENABLED, WORKSPACE_FAIRNESS_MAX_PERCENT, WORKSPACE_FAIRNESS_MIN_TOTAL,
WORKSPACE_MAX_QUEUED_JOBS, WORKSPACE_MAX_QUEUED_JOBS_DEFAULT,
},
KillpillSender, AUDIT_LOG_RETENTION_DAYS, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE,
CRITICAL_ALERTS_ON_TOKEN_EXPIRY, CRITICAL_ALERT_MUTE_UI_ENABLED,
@@ -312,6 +312,9 @@ pub async fn initial_load(
pass.setting(PREVIEW_TAGS_OVERRIDE_SETTING, false, |v| async move {
apply_preview_tags_override(v)
});
pass.setting(DEPENDENCY_JOB_TAG_SETTING, false, |v| async move {
apply_dependency_job_tag(v)
});
// Load per-workspace retention overrides before the first cleanup tick so a fresh server
// never sweeps globally without honoring configured longer-retention workspaces.
@@ -876,6 +879,24 @@ pub fn apply_preview_tags_override(value: Option<serde_json::Value>) {
}
}
pub async fn load_dependency_job_tag(db: &DB) -> error::Result<()> {
let v = load_value_from_global_settings(db, DEPENDENCY_JOB_TAG_SETTING).await?;
apply_dependency_job_tag(v);
Ok(())
}
pub fn apply_dependency_job_tag(value: Option<serde_json::Value>) {
let tag = match value {
Some(serde_json::Value::String(t)) if !t.trim().is_empty() => Some(t.trim().to_string()),
None | Some(serde_json::Value::Null) | Some(serde_json::Value::String(_)) => None,
Some(other) => {
tracing::error!("{DEPENDENCY_JOB_TAG_SETTING} is not a string: {other}, ignoring");
return;
}
};
DEPENDENCY_JOB_TAG.store(std::sync::Arc::new(tag));
}
// Upper bound on the duration window. Postgres `make_interval(secs => $1::int4)` is the consumer
// downstream, so this stays comfortably below `i32::MAX` and the subsequent `u32 -> i32` cast in
// `workspace_fairness::refresh_overloaded` cannot wrap into a negative interval (which would
+65
View File
@@ -0,0 +1,65 @@
//! Sets the process-wide `DEPENDENCY_JOB_TAG`, so it lives in its own test binary: any other
//! test queuing a dependency job in the same process would land on the overridden tag.
use sqlx::{Pool, Postgres};
use windmill_common::worker::DEPENDENCY_JOB_TAG;
use windmill_dep_map::scoped_dependency_map::DependencyDependent;
#[sqlx::test(fixtures("base"))]
async fn relocks_go_to_the_configured_dependency_job_tag(db: Pool<Postgres>) -> anyhow::Result<()> {
DEPENDENCY_JOB_TAG.store(std::sync::Arc::new(Some("deps-$workspace".to_string())));
for (path, hash, tag) in [
("f/tags/tagged", 7_100_001_i64, Some("default-$workspace")),
("f/tags/plain", 7_100_002_i64, None),
] {
sqlx::query(
"INSERT INTO script(workspace_id, created_by, content, schema, summary, description, path, hash, language, tag)
VALUES ('test-workspace', 'test-user', 'def main(): pass', '{}', '', '', $1, $2, 'python3', $3)",
)
.bind(path)
.bind(hash)
.bind(tag)
.execute(&db)
.await?;
}
let importers = ["f/tags/tagged", "f/tags/plain"]
.map(|p| DependencyDependent {
importer_path: p.to_string(),
importer_kind: "script".to_string(),
importer_node_ids: None,
})
.to_vec();
windmill_dep_map::trigger_dependents::trigger_dependents_to_recompute_dependencies(
"test-workspace",
importers,
None,
None,
"test@windmill.dev",
"test-user",
"u/test-user",
&db,
vec![],
)
.await?;
let tags: Vec<(String, String)> = sqlx::query_as(
"SELECT runnable_path, tag FROM v2_job WHERE kind = 'dependencies' ORDER BY runnable_path",
)
.fetch_all(&db)
.await?;
assert_eq!(
tags,
vec![
(
"f/tags/plain".to_string(),
"deps-test-workspace".to_string()
),
(
"f/tags/tagged".to_string(),
"deps-test-workspace".to_string()
),
]
);
Ok(())
}
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.820.0
version: 1.821.0
title: Windmill API
contact:
@@ -9,6 +9,7 @@ pub const DEFAULT_TAGS_WORKSPACES_SETTING: &str = "default_tags_workspaces";
pub const FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING: &str =
"fork_workspace_tag_append_fork_suffix";
pub const PREVIEW_TAGS_OVERRIDE_SETTING: &str = "preview_tags_override";
pub const DEPENDENCY_JOB_TAG_SETTING: &str = "dependency_job_tag";
pub const BASE_URL_SETTING: &str = "base_url";
pub const WS_BASE_URL_SETTING: &str = "ws_base_url";
pub const OAUTH_SETTING: &str = "oauths";
@@ -275,6 +275,8 @@ pub struct GlobalSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_build_binary_tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dependency_job_tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ws_base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub github_app_webhook_base_url: Option<String>,
+3 -1
View File
@@ -28,7 +28,9 @@ use crate::utils::StripPath;
/// `push`'s default (the language tag). Every relock of a script must use this, or a relock
/// lands on a worker pool other than the one its deploy locked on and resolves packages
/// differently.
/// Dedicated workers are handled by `push`, which gives `dedicated_worker` precedence.
/// Dedicated workers are handled by `push`, which gives `dedicated_worker` precedence. Below
/// that, `push` replaces this result with the instance's `dependency_job_tag` setting when it is
/// set, except for bunnative.
pub fn dependency_job_tag(tag: Option<String>, language: &ScriptLang) -> Option<String> {
if tag.as_ref().is_some_and(|x| x.contains("$args[")) {
None
+1
View File
@@ -450,6 +450,7 @@ lazy_static::lazy_static! {
pub static ref DEFAULT_TAGS_WORKSPACES: arc_swap::ArcSwap<Option<Vec<String>>> = arc_swap::ArcSwap::from_pointee(None);
pub static ref FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX: AtomicBool = AtomicBool::new(false);
pub static ref PREVIEW_TAGS_OVERRIDE: AtomicBool = AtomicBool::new(false);
pub static ref DEPENDENCY_JOB_TAG: arc_swap::ArcSwap<Option<String>> = arc_swap::ArcSwap::from_pointee(None);
pub static ref MAX_TIMEOUT: u64 = std::env::var("TIMEOUT")
.ok()
+46 -12
View File
@@ -81,9 +81,9 @@ use windmill_common::{
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
utils::{not_found_if_none, report_critical_error, StripPath, WarnAfterExt},
worker::{
to_raw_value, CLOUD_HOSTED, DISABLE_FLOW_SCRIPT, NO_LOGS, PREVIEW_TAGS_OVERRIDE,
WORKER_PULL_QUERIES, WORKER_PULL_QUERIES_FAIRNESS, WORKER_SUSPENDED_PULL_QUERY,
WORKSPACE_FAIRNESS_OVERLOADED,
to_raw_value, CLOUD_HOSTED, DEPENDENCY_JOB_TAG, DISABLE_FLOW_SCRIPT, NO_LOGS,
PREVIEW_TAGS_OVERRIDE, WORKER_PULL_QUERIES, WORKER_PULL_QUERIES_FAIRNESS,
WORKER_SUSPENDED_PULL_QUERY, WORKSPACE_FAIRNESS_OVERLOADED,
},
DB, METRICS_ENABLED,
};
@@ -4611,15 +4611,27 @@ pub async fn pull(
&& !(job.kind.is_preview()
&& PREVIEW_TAGS_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed))
{
let effective_ws = per_workspace_tag(&job.workspace_id, db).await;
let base_tag = if job.is_flow() {
"flow".to_string()
} else {
"dependency".to_string()
};
let tag = match &effective_ws {
Some(ws) => format!("{}-{}", base_tag, ws),
None => base_tag,
let tag = match configured_dependency_job_tag(
&job.kind,
job.script_lang.as_ref(),
&job.workspace_id,
db,
)
.await
{
Some(tag) => tag,
None => {
let effective_ws = per_workspace_tag(&job.workspace_id, db).await;
let base_tag = if job.is_flow() {
"flow".to_string()
} else {
"dependency".to_string()
};
match &effective_ws {
Some(ws) => format!("{}-{}", base_tag, ws),
None => base_tag,
}
}
};
sqlx::query!(
"UPDATE v2_job_queue SET tag = $1, running = false WHERE id = $2",
@@ -5267,6 +5279,24 @@ pub async fn resolve_push_tag(
Some(interpolate_args(tag.to_string(), args, &tag_ws))
}
/// The tag a dependency job is routed to when the instance's `dependency_job_tag` setting is
/// set, whatever tag the script, flow or app itself runs on. `None` for any other job kind, for
/// bunnative (its bundle must be built on a worker with the bun tag), or when the setting is
/// unset.
pub async fn configured_dependency_job_tag(
job_kind: &JobKind,
language: Option<&ScriptLang>,
workspace_id: &str,
db: &DB,
) -> Option<String> {
if !job_kind.is_dependency() || language == Some(&ScriptLang::Bunnative) {
return None;
}
let tag = DEPENDENCY_JOB_TAG.load_full();
let tag = tag.as_deref()?;
resolve_push_tag(tag, &PushArgs::from(&HashMap::new()), workspace_id, db).await
}
/// Refuses a `tag` the caller chose that the instance's custom tags do not let `w_id` use,
/// judging the queue it resolves to. `args` must be the ones the job is pushed with: resolving
/// with any others checks a queue the job does not land on.
@@ -7183,6 +7213,10 @@ async fn push_inner<'c, 'd>(
runnable_path.clone().expect("dedicated script has a path")
);
windmill_common::worker::dedicated_worker_tag(workspace_id, &full_path)
} else if let Some(tag) =
configured_dependency_job_tag(&job_kind, language.as_ref(), workspace_id, db).await
{
tag
} else {
let interpolated_tag = match tag {
Some(x) => resolve_push_tag(&x, &args, workspace_id, db).await,
+1
View File
@@ -101,6 +101,7 @@ mappable-rc.workspace = true
git-version.workspace = true
once_cell.workspace = true
tokio-postgres.workspace = true
postgres-protocol.workspace = true
bit-vec.workspace = true
url.workspace = true
async-stream.workspace = true
+160 -6
View File
@@ -1,5 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::net::IpAddr;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
@@ -362,6 +361,79 @@ fn wrap_param_encoding_error(
to_anyhow(err).into()
}
/// Whether a statement may return a row stream, and so needs its column types described
/// before it runs. Anything not recognised as returning at most a row counts as a stream:
/// a wrong guess only costs a round trip, while a missed stream can hang the job.
fn can_stream_rows(query: &str) -> bool {
let Some(keyword) = leading_keyword(query) else {
return true;
};
let rowless = matches!(
keyword.as_str(),
"insert"
| "update"
| "delete"
| "merge"
| "create"
| "alter"
| "drop"
| "truncate"
| "grant"
| "revoke"
| "comment"
| "set"
| "reset"
| "call"
| "do"
| "begin"
| "commit"
| "rollback"
| "lock"
| "vacuum"
| "analyze"
| "refresh"
);
!rowless || query.to_ascii_lowercase().contains("returning")
}
/// The first keyword of a statement, past whitespace and comments, following Postgres's
/// scanner: a `--` comment ends at CR or LF, and block comments nest, so
/// `/* /* a */ INSERT */ SELECT` starts with SELECT. `None` when the comments never end.
fn leading_keyword(query: &str) -> Option<String> {
let mut rest = query;
loop {
rest = rest.trim_start();
if let Some(after) = rest.strip_prefix("--") {
rest = &after[after.find(['\n', '\r'])?..];
} else if rest.starts_with("/*") {
let mut depth = 0usize;
let mut i = 0;
let bytes = rest.as_bytes();
loop {
match bytes.get(i..i + 2)? {
b"/*" => depth += 1,
b"*/" => depth -= 1,
_ => {
i += 1;
continue;
}
}
i += 2;
if depth == 0 {
break;
}
}
rest = &rest[i..];
} else {
break;
}
}
let end = rest
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '$'))
.unwrap_or(rest.len());
Some(rest[..end].to_ascii_lowercase())
}
fn otyp_to_pg_type(otyp: &str) -> error::Result<Type> {
let base = otyp.trim_end_matches("[]");
let is_array = otyp.ends_with("[]");
@@ -510,15 +582,25 @@ fn do_postgresql_inner<'a>(
let result_f = async move {
let mut res: Vec<Box<serde_json::value::RawValue>> = vec![];
// Always prefer query_typed_raw (unnamed prepared statement). It is sent as
// a single Parse+Bind+Execute+Sync round-trip, so it survives transaction-mode
// connection poolers (PgBouncer/Supabase pooler/RDS Proxy) where named
// Always prefer query_typed_raw (unnamed prepared statement). It and its
// describe are each a self-contained round-trip ending in Sync, so they
// survive transaction-mode connection poolers (PgBouncer/Supabase
// pooler/RDS Proxy) where named
// statements ("s0", "s1", ...) can be reported missing because the prepare
// and the execute land on different backend connections. Fall back to
// prepare + query_raw only when an arg has a type unsupported by
// otyp_to_pg_type (e.g. custom enum, geometry, …) — in that case we lose
// pooler safety, but the query at least runs against a direct connection.
let rows = if all_types_resolved {
// query_typed_raw looks up result column types this connection has not seen
// (enums, domains, extension types) while the rows already stream, and on a
// large result that lookup waits behind them forever. Describing first resolves
// them up front, still without a named statement.
if can_stream_rows(&query) {
if let Err(e) = client.describe_typed(&query, &param_types).await {
return Err(wrap_param_encoding_error(e, &param_meta, &param_types));
}
}
let typed_params = query_params
.iter()
.zip(param_types.iter())
@@ -1889,8 +1971,8 @@ pub fn pg_cell_to_json_value_with_state(
Type::UUID => get_basic(row, column, column_i, |a: uuid::Uuid| {
Ok(JSONValue::String(a.to_string()))
})?,
Type::INET => get_basic(row, column, column_i, |a: IpAddr| {
Ok(JSONValue::String(a.to_string()))
Type::INET | Type::CIDR => get_basic(row, column, column_i, |a: InetStr| {
Ok(JSONValue::String(a.0))
})?,
Type::INTERVAL => get_basic(row, column, column_i, |a: IntervalStr| {
Ok(JSONValue::String(a.0))
@@ -1997,6 +2079,9 @@ pub fn pg_cell_to_json_value_with_state(
Type::BYTEA_ARRAY => get_array(row, column, column_i, |a: Vec<u8>| {
Ok(JSONValue::String(format!("\\x{}", hex::encode(a))))
})?,
Type::INET_ARRAY | Type::CIDR_ARRAY => get_array(row, column, column_i, |a: InetStr| {
Ok(JSONValue::String(a.0))
})?,
Type::VOID => JSONValue::Null,
// Default fallback for unhandled column types: read as text. We use
// `AnyTextValue` instead of plain `String` so that `Kind::Enum`,
@@ -2156,6 +2241,29 @@ fn get_array<'a, T: FromSql<'a>>(
})
}
/// `inet` / `cidr` in Postgres's own text form. `IpAddr`'s `FromSql` drops the
/// prefix length, so a network such as `10.0.0.0/8` would read back as a host.
struct InetStr(String);
impl FromSql<'_> for InetStr {
fn from_sql(
ty: &Type,
raw: &[u8],
) -> Result<InetStr, Box<dyn std::error::Error + Sync + Send>> {
let inet = postgres_protocol::types::inet_from_sql(raw)?;
let host_prefix = if inet.addr().is_ipv4() { 32 } else { 128 };
Ok(InetStr(
if *ty == Type::CIDR || inet.netmask() != host_prefix {
format!("{}/{}", inet.addr(), inet.netmask())
} else {
inet.addr().to_string()
},
))
}
fn accepts(ty: &Type) -> bool {
matches!(*ty, Type::INET | Type::CIDR)
}
}
// you can remove this section if not using TS_VECTOR (or other types requiring an intermediary `FromSQL` struct)
struct StringCollector(String);
impl FromSql<'_> for StringCollector {
@@ -2271,6 +2379,19 @@ mod tests {
);
}
#[test]
fn inet_keeps_the_prefix_postgres_prints() {
let inet = |netmask: u8, addr: [u8; 4]| {
let mut raw = vec![2, netmask, 0, 4];
raw.extend(addr);
raw
};
let read = |ty: &Type, raw: &[u8]| InetStr::from_sql(ty, raw).unwrap().0;
assert_eq!(read(&Type::INET, &inet(32, [10, 1, 2, 3])), "10.1.2.3");
assert_eq!(read(&Type::INET, &inet(8, [10, 0, 0, 0])), "10.0.0.0/8");
assert_eq!(read(&Type::CIDR, &inet(32, [10, 1, 2, 3])), "10.1.2.3/32");
}
#[test]
fn test_parse_naive_date() {
// chrono's NaiveDate::to_string() format
@@ -2472,6 +2593,39 @@ mod tests {
windmill_parser_sql::parse_pg_typ(arg_t)
}
#[test]
fn only_rowless_statements_skip_the_describe() {
for q in [
"SELECT * FROM t",
"-- $1 n (int)\nselect $1",
"WITH x AS (DELETE FROM t RETURNING *) SELECT * FROM x",
"INSERT INTO t VALUES (1) RETURNING id",
"/* c */ UPDATE t SET a = 1\nreturning *",
"UPDATE t SET note = $$a;b$$ RETURNING *",
"/* /* inner */ INSERT */ SELECT * FROM t",
"/* never closed INSERT",
"-- INSERT",
"-- header\rSELECT m, pad AS\nupdate FROM t",
"insert_rows()",
"TABLE t",
"VALUES (1)",
"EXPLAIN SELECT 1",
] {
assert!(can_stream_rows(q), "{q}");
}
for q in [
"INSERT INTO t VALUES (1)",
"-- $1 n (int)\nUPDATE t SET a = $1",
"delete from t",
"CREATE TABLE t (a int)",
"SET search_path TO x",
"CALL p()",
"/* a /* b */ c */\n-- d\nINSERT INTO t VALUES (1)",
] {
assert!(!can_stream_rows(q), "{q}");
}
}
#[test]
fn convert_val_null_for_every_known_arg_t() {
// `Value::Null` for every type the parser may resolve, plus an unknown
+1 -1
View File
@@ -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.820.0";
export const VERSION = "v1.821.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-chat",
"version": "1.820.0",
"version": "1.821.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-chat",
"version": "1.820.0",
"version": "1.821.0",
"license": "Apache-2.0",
"devDependencies": {
"@ai-sdk/react": "^4.0.102",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-chat",
"description": "Build chat interfaces on Windmill flows deployed in chat mode, from any frontend or raw app",
"version": "1.820.0",
"version": "1.821.0",
"author": "Ruben Fiszel",
"license": "Apache-2.0",
"homepage": "https://github.com/windmill-labs/windmill/tree/main/chat-sdk#readme",
+1 -1
View File
@@ -10,4 +10,4 @@ export const WM_FORK_PREFIX = "wm-fork";
// (e.g. utils.ts) can read it without importing main.ts and creating a circular
// dependency (main → workspace → utils → main) that triggers a TDZ.
// Re-exported from main.ts for backwards compatibility.
export const VERSION = "1.820.0";
export const VERSION = "1.821.0";
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@windmill-labs/components",
"version": "1.820.0",
"version": "1.821.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@windmill-labs/components",
"version": "1.820.0",
"version": "1.821.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill-labs/components",
"version": "1.820.0",
"version": "1.821.0",
"scripts": {
"dev": "vite dev",
"dev:ui-builder": "mv static/ui_builder static/ui_builder.dev-disabled 2>/dev/null || true ; trap 'mv static/ui_builder.dev-disabled static/ui_builder 2>/dev/null || true' EXIT ; vite dev",
@@ -8,8 +8,10 @@
DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING,
FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING,
PREVIEW_TAGS_OVERRIDE_SETTING
PREVIEW_TAGS_OVERRIDE_SETTING,
DEPENDENCY_JOB_TAG_SETTING
} from '$lib/consts'
import TextInput from './text_input/TextInput.svelte'
import Toggle from './Toggle.svelte'
import MultiSelect from './select/MultiSelect.svelte'
import { safeSelectItems } from './select/utils.svelte'
@@ -31,12 +33,14 @@
let limitToWorkspaces = $state(false)
let previewTagsOverride = $state(false)
let forkAppendForkSuffix = $state(false)
let dependencyJobTag = $state('')
// Change detection
let originalDefaultTagPerWorkspace = $state<boolean | undefined>(defaultTagPerWorkspace)
let originalDefaultTagWorkspaces = $state<string[]>(defaultTagWorkspaces)
let originalPreviewTagsOverride = $state(false)
let originalForkAppendForkSuffix = $state(false)
let originalDependencyJobTag = $state('')
// Detect changes
let hasChanges = $derived(
@@ -44,7 +48,8 @@
JSON.stringify($state.snapshot(originalDefaultTagWorkspaces)?.sort() || []) !==
JSON.stringify($state.snapshot(defaultTagWorkspaces)?.sort() || []) ||
originalPreviewTagsOverride !== previewTagsOverride ||
originalForkAppendForkSuffix !== forkAppendForkSuffix
originalForkAppendForkSuffix !== forkAppendForkSuffix ||
originalDependencyJobTag.trim() !== dependencyJobTag.trim()
)
let workspaces: string[] = $state([])
@@ -70,6 +75,11 @@
})) as any
forkAppendForkSuffix = forkSetting ?? false
originalForkAppendForkSuffix = forkAppendForkSuffix
dependencyJobTag =
((await SettingService.getGlobal({
key: DEPENDENCY_JOB_TAG_SETTING
})) as any) ?? ''
originalDependencyJobTag = dependencyJobTag
} catch (err) {
sendUserToast(`Could not load default tags: ${err}`, true)
}
@@ -104,11 +114,19 @@
}
})
await SettingService.setGlobal({
key: DEPENDENCY_JOB_TAG_SETTING,
requestBody: {
value: dependencyJobTag.trim() || undefined
}
})
// Update original state after save
originalDefaultTagPerWorkspace = defaultTagPerWorkspace
originalDefaultTagWorkspaces = [...(defaultTagWorkspaces || [])]
originalPreviewTagsOverride = previewTagsOverride
originalForkAppendForkSuffix = forkAppendForkSuffix
originalDependencyJobTag = dependencyJobTag
loadDefaultTags()
sendUserToast('Saved')
@@ -218,6 +236,33 @@
disabled={!$enterpriseLicense}
/>
</div>
<div class="flex flex-col gap-1 max-w-md">
<span class="text-xs font-semibold text-emphasis">Dependency job tag</span>
<span class="text-2xs text-secondary">
Route every dependency job (lockfile resolution at deploy and relocks triggered by an
imported script changing, for scripts, flows and apps) to this tag, whatever tag the
runnable itself runs on. Supports <code>$workspace</code>. Dedicated workers still build
their own locks, and Bun native scripts still build their bundle on the bun tag.
</span>
<span class="text-2xs text-secondary">
Leave empty for the default routing: a script's dependency job runs on the script's own
tag, or on its language's default tag (e.g. <code
>{defaultTagPerWorkspace ? 'python3-$workspace' : 'python3'}</code
>) when it has none; flow and app dependency jobs run on
<code>{defaultTagPerWorkspace ? 'dependency-$workspace' : 'dependency'}</code>.
<a
href="https://www.windmill.dev/docs/core_concepts/worker_groups"
target="_blank"
class="gap-1 items-baseline"
>Worker groups <ExternalLink size={12} class="inline-block" /></a
>
</span>
<TextInput
bind:value={dependencyJobTag}
size="sm"
inputProps={{ placeholder: 'e.g. dependency', disabled: !$enterpriseLicense }}
/>
</div>
</div>
<div class="flex gap-2 items-center mb-1">
@@ -240,6 +285,17 @@
</div>
</div>
{/each}
{#if dependencyJobTag.trim()}
<div class="flex gap-2 items-center">
<div class="w-36">
<Badge color="transparent">all dependency jobs</Badge>
</div>
<div class="w-6 flex justify-center text-secondary">&rightarrow;</div>
<div class="flex-1">
<Badge color="blue">{dependencyJobTag.trim()}</Badge>
</div>
</div>
{/if}
{#if previewTagsOverride}
<div class="flex gap-2 items-center">
<div class="w-36">
+1
View File
@@ -47,6 +47,7 @@ export const DEFAULT_TAGS_PER_WORKSPACE_SETTING = 'default_tags_per_workspace'
export const DEFAULT_TAGS_WORKSPACES_SETTING = 'default_tags_workspaces'
export const FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING = 'fork_workspace_tag_append_fork_suffix'
export const PREVIEW_TAGS_OVERRIDE_SETTING = 'preview_tags_override'
export const DEPENDENCY_JOB_TAG_SETTING = 'dependency_job_tag'
export const WORKSPACE_FAIRNESS_ENABLED_SETTING = 'workspace_fairness_enabled'
export const WORKSPACE_FAIRNESS_MAX_PERCENT_SETTING = 'workspace_fairness_max_percent'
+1 -1
View File
@@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.820.0"
wmill = ">=1.821.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: '3.0.3'
info:
version: 1.820.0
version: 1.821.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.820.0'
ModuleVersion = '1.821.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.820.0"
version = "1.821.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.820.0",
"version": "1.821.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts", "./wacError.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.820.0",
"version": "1.821.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"homepage": "https://github.com/windmill-labs/windmill/tree/main/typescript-client#readme",
+1 -1
View File
@@ -1 +1 @@
1.820.0
1.821.0
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-yaml-validator",
"version": "1.820.0",
"version": "1.821.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-yaml-validator",
"version": "1.820.0",
"version": "1.821.0",
"license": "Apache 2.0",
"dependencies": {
"@stoplight/yaml": "^4.3.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-yaml-validator",
"version": "1.820.0",
"version": "1.821.0",
"description": "YAML validator for Windmill flow, schedule, and trigger files",
"main": "dist/index.js",
"types": "dist/index.d.ts",