Compare commits
5 Commits
v1.633.0
...
rf/testbac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9bac09b14 | ||
|
|
607a861b80 | ||
|
|
5ecfa92ac3 | ||
|
|
a3d5ae5425 | ||
|
|
d363fa6098 |
16
.github/workflows/backend-test.yml
vendored
16
.github/workflows/backend-test.yml
vendored
@@ -19,7 +19,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
cargo_test:
|
||||
runs-on: ubicloud-standard-16
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -50,9 +50,9 @@ jobs:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.21.5
|
||||
go-version: 1.25.0
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.8
|
||||
@@ -72,11 +72,13 @@ jobs:
|
||||
bundler-cache: false
|
||||
- name: Install PowerShell, mold and clang
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y powershell mold clang libcurl4-openssl-dev
|
||||
dotnet tool install --global PowerShell
|
||||
echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
|
||||
sudo apt-get update && sudo apt-get install -y mold clang libcurl4-openssl-dev
|
||||
working-directory: /
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: false
|
||||
cache: true
|
||||
toolchain: 1.93.0
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
@@ -214,7 +216,7 @@ jobs:
|
||||
RUST_LOG: "off"
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_BUILD_JOBS: 12
|
||||
CARGO_BUILD_JOBS: 16
|
||||
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
|
||||
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
|
||||
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
|
||||
@@ -222,4 +224,4 @@ jobs:
|
||||
run: |
|
||||
deno --version && bun -v && node --version && go version && python3 --version && php --version && ruby --version && pwsh --version && dotnet --version
|
||||
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp --all -- --nocapture --test-threads=10
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp --all -- --nocapture --test-threads=12
|
||||
|
||||
23
.github/workflows/check-org-membership.yml
vendored
23
.github/workflows/check-org-membership.yml
vendored
@@ -4,10 +4,9 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commenter:
|
||||
required: false
|
||||
required: true
|
||||
type: string
|
||||
default: ''
|
||||
description: 'The username to check. Auto-detected from the event context if not provided.'
|
||||
description: 'The username to check for organization membership'
|
||||
organization:
|
||||
required: false
|
||||
type: string
|
||||
@@ -33,27 +32,11 @@ 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: ${{ steps.determine-commenter.outputs.commenter }}
|
||||
COMMENTER: ${{ inputs.commenter }}
|
||||
ORG: ${{ inputs.organization }}
|
||||
TRUSTED_BOT: ${{ inputs.trusted_bot }}
|
||||
run: |
|
||||
|
||||
26
.github/workflows/claude-fast.yml
vendored
26
.github/workflows/claude-fast.yml
vendored
@@ -11,18 +11,40 @@ on:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
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: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-8
|
||||
|
||||
26
.github/workflows/claude-plan.yml
vendored
26
.github/workflows/claude-plan.yml
vendored
@@ -11,18 +11,40 @@ on:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
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: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-4
|
||||
|
||||
26
.github/workflows/claude.yml
vendored
26
.github/workflows/claude.yml
vendored
@@ -11,18 +11,40 @@ on:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
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: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
if: |
|
||||
needs.check-membership.outputs.is_member == 'true'
|
||||
runs-on: ubicloud-standard-8
|
||||
|
||||
39
.github/workflows/create-docs.yml
vendored
Normal file
39
.github/workflows/create-docs.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
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 }}
|
||||
53
.github/workflows/git-commands.yaml
vendored
53
.github/workflows/git-commands.yaml
vendored
@@ -5,21 +5,8 @@ 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:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/updatesqlx')
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/updatesqlx')
|
||||
runs-on: ubicloud-standard-8
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -134,8 +121,7 @@ jobs:
|
||||
})
|
||||
|
||||
demo:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/demo')
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/demo')
|
||||
runs-on: ubicloud-standard-2
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -214,8 +200,7 @@ jobs:
|
||||
fi
|
||||
|
||||
update-ee-ref:
|
||||
needs: check-membership
|
||||
if: needs.check-membership.outputs.is_member == 'true' && startsWith(github.event.comment.body, '/eeref')
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/eeref')
|
||||
runs-on: ubicloud-standard-2
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -298,35 +283,3 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
body: 'Successfully updated ee-repo-ref.txt'
|
||||
})
|
||||
|
||||
update-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
|
||||
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 -
|
||||
|
||||
26
.github/workflows/spawn-ephemeral-backend.yml
vendored
26
.github/workflows/spawn-ephemeral-backend.yml
vendored
@@ -13,16 +13,38 @@ on:
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
check-membership:
|
||||
determine-commenter:
|
||||
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: check-membership
|
||||
needs: [determine-commenter, check-membership]
|
||||
# Only run on PR comments that contain /spawn-backend, or manual dispatch
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,31 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.633.0](https://github.com/windmill-labs/windmill/compare/v1.632.0...v1.633.0) (2026-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* /health endpoints ([#7727](https://github.com/windmill-labs/windmill/issues/7727)) ([7df4aa4](https://github.com/windmill-labs/windmill/commit/7df4aa4fec021fc728950fc10db4d0401a205087))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* save deployment msg in CE ([#7923](https://github.com/windmill-labs/windmill/issues/7923)) ([e9be616](https://github.com/windmill-labs/windmill/commit/e9be616d3c079a0c6fd98733c66560f2cc1ee40d))
|
||||
|
||||
## [1.632.0](https://github.com/windmill-labs/windmill/compare/v1.631.2...v1.632.0) (2026-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ai:** add AWS bedrock session token support ([#7908](https://github.com/windmill-labs/windmill/issues/7908)) ([d95e4db](https://github.com/windmill-labs/windmill/commit/d95e4db8f31e70a645a5f41e287557933b257db8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add kafka kerberos runtime packages ([#7918](https://github.com/windmill-labs/windmill/issues/7918)) ([22f22c2](https://github.com/windmill-labs/windmill/commit/22f22c26612b904c2b82b415ec9337213ef593c3))
|
||||
* **frontend:** redesign instance settings ([#7916](https://github.com/windmill-labs/windmill/issues/7916)) ([dd419ad](https://github.com/windmill-labs/windmill/commit/dd419ade94a992073dfd1a979bfeb8a5fadfb051))
|
||||
* hash long dedicated worker tags ([#7914](https://github.com/windmill-labs/windmill/issues/7914)) ([aaa1b92](https://github.com/windmill-labs/windmill/commit/aaa1b92300bdc0794de5356ef6750bab5d8d81a0))
|
||||
|
||||
## [1.631.2](https://github.com/windmill-labs/windmill/compare/v1.631.1...v1.631.2) (2026-02-11)
|
||||
|
||||
|
||||
|
||||
21
CLAUDE.md
21
CLAUDE.md
@@ -17,32 +17,19 @@ When implementing new features in Windmill, follow these best practices:
|
||||
|
||||
## Language-Specific Guides
|
||||
|
||||
- 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.
|
||||
- Backend (Rust): @backend/rust-best-practices.mdc + @backend/summarized_schema.txt
|
||||
- Frontend (Svelte 5): @frontend/svelte5-best-practices.mdc
|
||||
|
||||
## Querying the Database
|
||||
|
||||
`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:
|
||||
To query the database directly, use psql with the following connection string:
|
||||
|
||||
```bash
|
||||
psql postgres://postgres:changeme@localhost:5432/windmill
|
||||
```
|
||||
|
||||
Useful psql commands:
|
||||
- `\d <table_name>` — full table definition with indexes and constraints
|
||||
- `\di <table_name>*` — list indexes for a table
|
||||
- `\d+ <table_name>` — extended table info including storage and descriptions
|
||||
This can be helpful for:
|
||||
|
||||
This is also helpful for:
|
||||
- Inspecting database state during development
|
||||
- Testing queries before implementing them in Rust
|
||||
- Debugging data-related issues
|
||||
|
||||
@@ -132,7 +132,6 @@ 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
|
||||
@@ -150,8 +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 \
|
||||
&& if echo "$features" | grep -q "ee"; then apt-get install -y --no-install-recommends libsasl2-modules-gssapi-mit krb5-user; fi \
|
||||
&& 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 clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,11 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +122,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -68,8 +67,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -52,8 +51,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -71,8 +70,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,8 +245,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9f07510019ebe6f0c5fa17bf31c2d14755474cba82b3b388a47585a8bb325b1a"
|
||||
}
|
||||
@@ -155,8 +155,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +160,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -72,8 +71,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM worker_ping WHERE ping_at > now() - interval '5 minutes'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bbf166552ba15ce7cd76f812cdf7223414fa5c8a4860f3fe829d649baa1c5465"
|
||||
}
|
||||
@@ -105,8 +105,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +105,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e004ebd5b5532a4b85984a62f8ad48a81aa3460c1ca07701f386135d72cdecf5"
|
||||
}
|
||||
@@ -185,8 +185,7 @@
|
||||
"sqs",
|
||||
"gcp",
|
||||
"mqtt",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -72,8 +71,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"mqtt",
|
||||
"gcp",
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -72,8 +71,7 @@
|
||||
"name": "native_trigger_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"nextcloud",
|
||||
"google"
|
||||
"nextcloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n worker_group,\n wm_version\n FROM worker_ping\n WHERE ping_at > now() - interval '5 minutes'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "worker_group",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "wm_version",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f912b91c940900b9961920ac0f71b7ba1e8cd50ce90c0c17336e9dd9c684f348"
|
||||
}
|
||||
@@ -49,24 +49,6 @@ 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
|
||||
|
||||
403
backend/Cargo.lock
generated
403
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.633.0"
|
||||
version = "1.631.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -74,7 +74,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.633.0"
|
||||
version = "1.631.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -95,8 +95,8 @@ lto = "thin"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-api/private", "windmill-api-agent-workers?/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private", "windmill-test-utils/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server", "dep:windmill-api-agent-workers", "windmill-test-utils/agent_worker_server"]
|
||||
private = ["windmill-api/private", "windmill-api-agent-workers?/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server", "dep:windmill-api-agent-workers"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-api-agent-workers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
local_reports = ["windmill-common/local_reports"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
|
||||
@@ -112,7 +112,7 @@ cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud
|
||||
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "windmill-common/tantivy", "enterprise", "parquet"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:windmill-runtime-nativets", "windmill-test-utils/deno_core"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:windmill-runtime-nativets"]
|
||||
deno_core_mac = ["deno_core", "windmill-worker/libffi_mac"]
|
||||
kafka = ["windmill-api/kafka"]
|
||||
kafka-gssapi = ["windmill-api/kafka-gssapi"]
|
||||
@@ -137,7 +137,7 @@ scoped_cache = ["windmill-common/scoped_cache"]
|
||||
no_auth = ["windmill-api/no_auth"]
|
||||
private_registry_test = []
|
||||
# Languages
|
||||
python = ["windmill-worker/python", "windmill-api/python", "windmill-test-utils/python"]
|
||||
python = ["windmill-worker/python", "windmill-api/python"]
|
||||
rust = ["windmill-worker/rust"]
|
||||
mysql = ["windmill-worker/mysql"]
|
||||
oracledb = ["windmill-worker/oracledb"]
|
||||
@@ -181,8 +181,7 @@ 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",
|
||||
"postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "smtp", "stripe",
|
||||
"license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server", "bedrock", "native_trigger", "quickjs",
|
||||
"windmill-git-sync/all_sqlx_features"]
|
||||
"license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server", "bedrock", "native_trigger", "quickjs"]
|
||||
|
||||
[patch.crates-io]
|
||||
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
|
||||
|
||||
@@ -1 +1 @@
|
||||
e7f80bca9320580e1cb96b4f4ca9942649abce7f
|
||||
ab082a1a65830bf362a32b30fa86741999da091d
|
||||
@@ -328,7 +328,6 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
|
||||
"azure.keyvault.secrets" => "azure-keyvault-secrets",
|
||||
"azure.storage.blob" => "azure-storage-blob",
|
||||
"azure.storage.filedatalake" => "azure-storage-file-datalake",
|
||||
"azure.identity" => "azure-identity",
|
||||
// Add new entry here ^
|
||||
};
|
||||
|
||||
|
||||
@@ -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" --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"
|
||||
# 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"
|
||||
# Then you can run python summarize_schema.py schema.sql to get the summarized schema.
|
||||
|
||||
import re
|
||||
@@ -17,7 +17,6 @@ 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:
|
||||
@@ -55,9 +54,6 @@ 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})")
|
||||
|
||||
@@ -69,22 +65,16 @@ def summarize_schema(file_path):
|
||||
tables[current_table]['pks'].update(pk_cols)
|
||||
continue
|
||||
|
||||
# --- 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 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 Index definitions ---
|
||||
match_index = re.match(r"CREATE (UNIQUE )?INDEX (\w+) ON public\.(\w+) USING (\w+) \((.+)\);", line)
|
||||
@@ -104,73 +94,44 @@ 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 compact, LLM-friendly string.
|
||||
Formats the parsed schema data into a clean, readable string.
|
||||
"""
|
||||
output = []
|
||||
|
||||
output.append("# Database Schema")
|
||||
output.append("")
|
||||
output.append("## ENUMs")
|
||||
|
||||
output.append("### Simplified Database Schema ###")
|
||||
output.append("\n--- Custom Data Types (ENUMs) ---\n")
|
||||
if not enums:
|
||||
output.append("(none)")
|
||||
output.append("No custom ENUM types found.")
|
||||
else:
|
||||
for name, values in sorted(enums.items()):
|
||||
output.append(f"{name}: {', '.join(values)}")
|
||||
output.append(f"{name}:")
|
||||
for v in values:
|
||||
output.append(f" - {v}")
|
||||
output.append("")
|
||||
|
||||
output.append("")
|
||||
output.append("## Tables")
|
||||
output.append("\n--- Tables and Relationships ---\n")
|
||||
if not tables:
|
||||
output.append("(none)")
|
||||
output.append("No tables found.")
|
||||
else:
|
||||
for name, data in sorted(tables.items()):
|
||||
# Columns: inline, comma-separated, with PK marker and shortened types
|
||||
cols = []
|
||||
output.append(f"TABLE: {name}")
|
||||
for col in data['columns']:
|
||||
col_short = shorten_type(col)
|
||||
col_name = col.split(' ')[0]
|
||||
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
|
||||
marker = " (PK)" if col_name in data['pks'] else ""
|
||||
output.append(f" - {col}{marker}")
|
||||
|
||||
if data['fks']:
|
||||
output.append(f" FK: {' | '.join(data['fks'])}")
|
||||
|
||||
# Indexes omitted for brevity — query the database for exact index info
|
||||
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)
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,9 @@ if [[ "$(uname)" == "Darwin" ]]; then
|
||||
|
||||
# Run cargo sqlx prepare with deno_core_mac
|
||||
echo "Running cargo sqlx prepare with deno_core_mac..."
|
||||
cargo sqlx prepare --workspace -- --workspace --all-targets --features all_sqlx_features,private,deno_core_mac,deno_core,enterprise,mcp
|
||||
cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private,deno_core_mac
|
||||
else
|
||||
# Run cargo sqlx prepare
|
||||
echo "Running cargo sqlx prepare..."
|
||||
cargo sqlx prepare --workspace -- --workspace --all-targets --features all_sqlx_features,ee,deno_core,private,enterprise,mcp
|
||||
cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,ee
|
||||
fi
|
||||
|
||||
@@ -12,10 +12,7 @@ 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;
|
||||
|
||||
@@ -862,13 +859,10 @@ 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 (
|
||||
@@ -1078,9 +1072,7 @@ async fn create_script_internal<'c>(
|
||||
}
|
||||
if needs_lock_gen {
|
||||
let tag = if ns.dedicated_worker.is_some_and(|x| x) {
|
||||
Some(windmill_common::worker::dedicated_worker_tag(
|
||||
&w_id, &ns.path,
|
||||
))
|
||||
Some(format!("{}:{}", &w_id, &ns.path,))
|
||||
} else if ns.tag.as_ref().is_some_and(|x| x.contains("$args[")) {
|
||||
None
|
||||
} else {
|
||||
@@ -1847,9 +1839,7 @@ 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(
|
||||
@@ -2052,9 +2042,7 @@ 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(
|
||||
@@ -2109,9 +2097,7 @@ 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)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.633.0
|
||||
version: 1.631.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -41,64 +41,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/health/status:
|
||||
get:
|
||||
summary: health status
|
||||
description: |
|
||||
Health status endpoint. Returns cached health status (database connectivity, worker count).
|
||||
Cache TTL is fixed at 5 seconds. Use force=true query parameter to bypass cache.
|
||||
Note: This endpoint is intentionally different from Kubernetes probes to avoid confusion.
|
||||
For k8s liveness/readiness probes, use /version endpoint.
|
||||
operationId: getHealthStatus
|
||||
tags:
|
||||
- health
|
||||
security: []
|
||||
parameters:
|
||||
- name: force
|
||||
in: query
|
||||
description: Force a fresh check, bypassing the cache
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
"200":
|
||||
description: server is healthy or degraded
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HealthStatusResponse"
|
||||
"503":
|
||||
description: server is unhealthy (database unreachable)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HealthStatusResponse"
|
||||
|
||||
/health/detailed:
|
||||
get:
|
||||
summary: detailed health status
|
||||
description: |
|
||||
Returns detailed health information including database pool stats, worker details, and queue status.
|
||||
Requires authentication. Use for monitoring dashboards and debugging.
|
||||
This endpoint always returns fresh data (no caching).
|
||||
operationId: getHealthDetailed
|
||||
tags:
|
||||
- health
|
||||
responses:
|
||||
"200":
|
||||
description: server is healthy or degraded
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DetailedHealthResponse"
|
||||
"503":
|
||||
description: server is unhealthy (database unreachable)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DetailedHealthResponse"
|
||||
|
||||
/uptodate:
|
||||
get:
|
||||
summary: is backend up to date
|
||||
@@ -17456,167 +17398,6 @@ components:
|
||||
# -- INLINE END --
|
||||
# Do not change line above
|
||||
|
||||
HealthStatusResponse:
|
||||
type: object
|
||||
description: Health status response (cached with 5s TTL)
|
||||
required:
|
||||
- status
|
||||
- checked_at
|
||||
- database_healthy
|
||||
- workers_alive
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [healthy, degraded, unhealthy]
|
||||
description: Overall health status
|
||||
checked_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when the health check was actually performed (not cache return time)
|
||||
database_healthy:
|
||||
type: boolean
|
||||
description: Whether the database is reachable
|
||||
workers_alive:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of workers that pinged within last 5 minutes
|
||||
|
||||
DetailedHealthResponse:
|
||||
type: object
|
||||
description: Detailed health status response (always fresh, no caching)
|
||||
required:
|
||||
- status
|
||||
- checked_at
|
||||
- version
|
||||
- checks
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [healthy, degraded, unhealthy]
|
||||
description: Overall health status
|
||||
checked_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when the health check was performed
|
||||
version:
|
||||
type: string
|
||||
description: Server version (e.g., "EE 1.615.3")
|
||||
checks:
|
||||
$ref: "#/components/schemas/HealthChecks"
|
||||
|
||||
HealthChecks:
|
||||
type: object
|
||||
description: Detailed health checks
|
||||
required:
|
||||
- database
|
||||
- readiness
|
||||
properties:
|
||||
database:
|
||||
$ref: "#/components/schemas/DatabaseHealth"
|
||||
workers:
|
||||
$ref: "#/components/schemas/WorkersHealth"
|
||||
description: Worker status (null if database is unreachable)
|
||||
nullable: true
|
||||
queue:
|
||||
$ref: "#/components/schemas/QueueHealth"
|
||||
description: Queue status (null if database is unreachable)
|
||||
nullable: true
|
||||
readiness:
|
||||
$ref: "#/components/schemas/ReadinessHealth"
|
||||
|
||||
DatabaseHealth:
|
||||
type: object
|
||||
description: Database health status
|
||||
required:
|
||||
- healthy
|
||||
- latency_ms
|
||||
- pool
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
description: Whether the database is reachable
|
||||
latency_ms:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Database query latency in milliseconds
|
||||
pool:
|
||||
$ref: "#/components/schemas/PoolStats"
|
||||
|
||||
PoolStats:
|
||||
type: object
|
||||
description: Database connection pool statistics
|
||||
required:
|
||||
- size
|
||||
- idle
|
||||
- max_connections
|
||||
properties:
|
||||
size:
|
||||
type: integer
|
||||
description: Current number of connections in the pool
|
||||
idle:
|
||||
type: integer
|
||||
description: Number of idle connections
|
||||
max_connections:
|
||||
type: integer
|
||||
description: Maximum number of connections allowed
|
||||
|
||||
WorkersHealth:
|
||||
type: object
|
||||
description: Workers health status
|
||||
required:
|
||||
- healthy
|
||||
- active_count
|
||||
- worker_groups
|
||||
- min_version
|
||||
- versions
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
description: Whether any workers are active
|
||||
active_count:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of active workers (pinged in last 5 minutes)
|
||||
worker_groups:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of active worker groups
|
||||
min_version:
|
||||
type: string
|
||||
description: Minimum required worker version
|
||||
versions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of active worker versions
|
||||
|
||||
QueueHealth:
|
||||
type: object
|
||||
description: Job queue status
|
||||
required:
|
||||
- pending_jobs
|
||||
- running_jobs
|
||||
properties:
|
||||
pending_jobs:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of pending jobs in the queue
|
||||
running_jobs:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Number of currently running jobs
|
||||
|
||||
ReadinessHealth:
|
||||
type: object
|
||||
description: Server readiness status
|
||||
required:
|
||||
- healthy
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
description: Whether the server is ready to accept requests
|
||||
|
||||
AutoInviteConfig:
|
||||
type: object
|
||||
description: Configuration for auto-inviting users to the workspace
|
||||
|
||||
@@ -166,12 +166,6 @@ struct AIStandardResource {
|
||||
deserialize_with = "empty_string_as_none"
|
||||
)]
|
||||
aws_secret_access_key: Option<String>,
|
||||
#[serde(
|
||||
alias = "awsSessionToken",
|
||||
default,
|
||||
deserialize_with = "empty_string_as_none"
|
||||
)]
|
||||
aws_session_token: Option<String>,
|
||||
/// Platform for Anthropic API (standard or google_vertex_ai)
|
||||
#[serde(default)]
|
||||
platform: AnthropicPlatform,
|
||||
@@ -205,8 +199,6 @@ struct AIRequestConfig {
|
||||
pub aws_access_key_id: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
pub aws_secret_access_key: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
pub aws_session_token: Option<String>,
|
||||
pub platform: AnthropicPlatform,
|
||||
pub enable_1m_context: bool,
|
||||
}
|
||||
@@ -227,7 +219,6 @@ impl AIRequestConfig {
|
||||
region,
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
aws_session_token,
|
||||
platform,
|
||||
enable_1m_context,
|
||||
) = match resource {
|
||||
@@ -262,11 +253,6 @@ 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,
|
||||
@@ -277,7 +263,6 @@ impl AIRequestConfig {
|
||||
region,
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
aws_session_token,
|
||||
platform,
|
||||
enable_1m_context,
|
||||
)
|
||||
@@ -300,7 +285,6 @@ impl AIRequestConfig {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
AnthropicPlatform::Standard,
|
||||
false,
|
||||
)
|
||||
@@ -316,7 +300,6 @@ impl AIRequestConfig {
|
||||
region,
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
aws_session_token,
|
||||
platform,
|
||||
enable_1m_context,
|
||||
})
|
||||
@@ -860,7 +843,6 @@ 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;
|
||||
@@ -869,7 +851,6 @@ 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;
|
||||
@@ -885,7 +866,6 @@ 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;
|
||||
@@ -896,7 +876,6 @@ 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;
|
||||
|
||||
@@ -71,11 +71,7 @@ struct OpenAIToolFunction {
|
||||
/// Authentication configuration for Bedrock clients
|
||||
enum BedrockAuthConfig {
|
||||
BearerToken(String),
|
||||
IamCredentials {
|
||||
access_key_id: String,
|
||||
secret_access_key: String,
|
||||
session_token: Option<String>,
|
||||
},
|
||||
IamCredentials { access_key_id: String, secret_access_key: String },
|
||||
Environment,
|
||||
}
|
||||
|
||||
@@ -84,7 +80,6 @@ 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())
|
||||
@@ -95,9 +90,6 @@ 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
|
||||
@@ -109,19 +101,12 @@ 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<BedrockClient> {
|
||||
match determine_auth_config(
|
||||
api_key,
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
aws_session_token,
|
||||
) {
|
||||
match determine_auth_config(api_key, aws_access_key_id, aws_secret_access_key) {
|
||||
BedrockAuthConfig::BearerToken(key) => BedrockClient::from_bearer_token(key, 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::IamCredentials { access_key_id, secret_access_key } => {
|
||||
BedrockClient::from_credentials(access_key_id, secret_access_key, None, region).await
|
||||
}
|
||||
BedrockAuthConfig::Environment => BedrockClient::from_env(region).await,
|
||||
}
|
||||
@@ -178,7 +163,6 @@ 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<aws_sdk_bedrock::Client> {
|
||||
use aws_config::BehaviorVersion;
|
||||
@@ -186,12 +170,7 @@ 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,
|
||||
aws_session_token,
|
||||
) {
|
||||
match determine_auth_config(api_key, aws_access_key_id, aws_secret_access_key) {
|
||||
BedrockAuthConfig::BearerToken(key) => {
|
||||
let config = aws_sdk_bedrock::config::Builder::new()
|
||||
.region(region_provider)
|
||||
@@ -200,11 +179,11 @@ async fn create_bedrock_control_client(
|
||||
.build();
|
||||
Ok(aws_sdk_bedrock::Client::from_conf(config))
|
||||
}
|
||||
BedrockAuthConfig::IamCredentials { access_key_id, secret_access_key, session_token } => {
|
||||
BedrockAuthConfig::IamCredentials { access_key_id, secret_access_key } => {
|
||||
let credentials = aws_credential_types::Credentials::new(
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
session_token,
|
||||
None,
|
||||
None,
|
||||
"windmill",
|
||||
);
|
||||
@@ -230,17 +209,11 @@ 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,
|
||||
aws_session_token,
|
||||
region,
|
||||
)
|
||||
.await?;
|
||||
let client =
|
||||
create_bedrock_control_client(api_key, aws_access_key_id, aws_secret_access_key, region)
|
||||
.await?;
|
||||
|
||||
let response = client
|
||||
.list_foundation_models()
|
||||
@@ -285,17 +258,11 @@ 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,
|
||||
aws_session_token,
|
||||
region,
|
||||
)
|
||||
.await?;
|
||||
let client =
|
||||
create_bedrock_control_client(api_key, aws_access_key_id, aws_secret_access_key, region)
|
||||
.await?;
|
||||
|
||||
let response =
|
||||
client.list_inference_profiles().send().await.map_err(|e| {
|
||||
@@ -348,21 +315,14 @@ 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,
|
||||
aws_session_token,
|
||||
region,
|
||||
)
|
||||
.await?;
|
||||
let bedrock_client =
|
||||
create_bedrock_client(api_key, aws_access_key_id, aws_secret_access_key, region).await?;
|
||||
|
||||
// Convert messages using shared conversion
|
||||
let (bedrock_messages, system_prompts) =
|
||||
@@ -608,21 +568,14 @@ 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,
|
||||
aws_session_token,
|
||||
region,
|
||||
)
|
||||
.await?;
|
||||
let bedrock_client =
|
||||
create_bedrock_client(api_key, aws_access_key_id, aws_secret_access_key, region).await?;
|
||||
|
||||
// Convert messages using shared conversion
|
||||
let (bedrock_messages, system_prompts) =
|
||||
@@ -801,60 +754,3 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,608 +0,0 @@
|
||||
/*
|
||||
* Author: Windmill Labs, Inc 2024
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Query},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
use windmill_common::min_version::MIN_KEEP_ALIVE_VERSION;
|
||||
use windmill_common::utils::GIT_VERSION;
|
||||
use windmill_common::IS_READY;
|
||||
|
||||
#[cfg(feature = "prometheus")]
|
||||
use windmill_common::METRICS_ENABLED;
|
||||
|
||||
/// Fixed 5 second cache TTL for health status
|
||||
const HEALTH_CACHE_TTL: Duration = Duration::from_secs(5);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref STATUS_CACHE: Arc<RwLock<Option<CachedHealthStatus>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
/// Environment variable to silence health endpoint logs
|
||||
static ref SILENCE_HEALTH_LOGS: bool = std::env::var("SILENCE_HEALTH_LOGS")
|
||||
.map(|v| v.to_lowercase() == "true" || v == "1")
|
||||
.unwrap_or(false);
|
||||
}
|
||||
|
||||
#[cfg(feature = "prometheus")]
|
||||
lazy_static::lazy_static! {
|
||||
/// Health status phase gauge with labels: healthy, degraded, unhealthy
|
||||
/// Only one label will be 1 at a time, others will be 0
|
||||
static ref HEALTH_STATUS_PHASE: Option<prometheus::IntGaugeVec> =
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
Some(prometheus::register_int_gauge_vec!(
|
||||
"health_status_phase",
|
||||
"Health status phase (1 = current state, 0 = not current state)",
|
||||
&["phase"]
|
||||
).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
/// Database latency in milliseconds
|
||||
static ref HEALTH_DATABASE_LATENCY: Option<prometheus::Gauge> =
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
Some(prometheus::register_gauge!(
|
||||
"health_database_latency_ms",
|
||||
"Database query latency in milliseconds"
|
||||
).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
/// Database unresponsive flag (1 = unresponsive, 0 = responsive)
|
||||
static ref HEALTH_DATABASE_UNRESPONSIVE: Option<prometheus::IntGauge> =
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
Some(prometheus::register_int_gauge!(
|
||||
"health_database_unresponsive",
|
||||
"Database unresponsive flag (1 = unresponsive, 0 = responsive)"
|
||||
).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
/// Database connection pool size
|
||||
static ref HEALTH_DATABASE_POOL_SIZE: Option<prometheus::IntGauge> =
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
Some(prometheus::register_int_gauge!(
|
||||
"health_database_pool_size",
|
||||
"Current number of connections in the database pool"
|
||||
).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
/// Database connection pool idle connections
|
||||
static ref HEALTH_DATABASE_POOL_IDLE: Option<prometheus::IntGauge> =
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
Some(prometheus::register_int_gauge!(
|
||||
"health_database_pool_idle",
|
||||
"Number of idle connections in the database pool"
|
||||
).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
/// Database connection pool max connections
|
||||
static ref HEALTH_DATABASE_POOL_MAX: Option<prometheus::IntGauge> =
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
Some(prometheus::register_int_gauge!(
|
||||
"health_database_pool_max",
|
||||
"Maximum connections allowed in the database pool"
|
||||
).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CachedHealthStatus {
|
||||
status: HealthStatusResponse,
|
||||
cached_at: std::time::Instant,
|
||||
}
|
||||
|
||||
/// Query parameters for status endpoint
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StatusQuery {
|
||||
/// Force a fresh check, bypassing the cache
|
||||
#[serde(default)]
|
||||
force: bool,
|
||||
}
|
||||
|
||||
/// Status endpoint - cached health status (unauthenticated)
|
||||
pub fn status_service() -> Router {
|
||||
Router::new().route("/", get(health_status))
|
||||
}
|
||||
|
||||
/// Detailed health endpoint - requires DB auth (always fresh)
|
||||
pub fn detailed_service() -> Router {
|
||||
Router::new().route("/", get(health_detailed))
|
||||
}
|
||||
|
||||
// ============ Response Types ============
|
||||
|
||||
#[derive(Serialize, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum HealthStatus {
|
||||
Healthy,
|
||||
Degraded,
|
||||
Unhealthy,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct HealthStatusResponse {
|
||||
pub status: HealthStatus,
|
||||
pub checked_at: DateTime<Utc>,
|
||||
pub database_healthy: bool,
|
||||
pub workers_alive: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DetailedHealthResponse {
|
||||
pub status: HealthStatus,
|
||||
pub checked_at: DateTime<Utc>,
|
||||
pub version: String,
|
||||
pub checks: HealthChecks,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct HealthChecks {
|
||||
pub database: DatabaseHealth,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workers: Option<WorkersHealth>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub queue: Option<QueueHealth>,
|
||||
pub readiness: ReadinessHealth,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DatabaseHealth {
|
||||
pub healthy: bool,
|
||||
pub latency_ms: i64,
|
||||
pub pool: PoolStats,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PoolStats {
|
||||
pub size: u32,
|
||||
pub idle: u32,
|
||||
pub max_connections: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct WorkersHealth {
|
||||
pub healthy: bool,
|
||||
pub active_count: i64,
|
||||
pub worker_groups: Vec<String>,
|
||||
pub min_version: String,
|
||||
pub versions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct QueueHealth {
|
||||
pub pending_jobs: u64,
|
||||
pub running_jobs: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ReadinessHealth {
|
||||
pub healthy: bool,
|
||||
}
|
||||
|
||||
// ============ Check Functions ============
|
||||
|
||||
const HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
/// Result of a database health check including latency
|
||||
struct DatabaseCheckResult {
|
||||
healthy: bool,
|
||||
latency_ms: i64,
|
||||
}
|
||||
|
||||
async fn check_database_with_latency(db: &DB) -> DatabaseCheckResult {
|
||||
let start = std::time::Instant::now();
|
||||
let healthy = tokio::time::timeout(
|
||||
HEALTH_CHECK_TIMEOUT,
|
||||
sqlx::query_scalar!("SELECT 1").fetch_one(db),
|
||||
)
|
||||
.await
|
||||
.map(|r| r.is_ok())
|
||||
.unwrap_or(false);
|
||||
let latency_ms = start.elapsed().as_millis() as i64;
|
||||
|
||||
DatabaseCheckResult { healthy, latency_ms }
|
||||
}
|
||||
|
||||
fn get_pool_stats(db: &DB) -> PoolStats {
|
||||
PoolStats {
|
||||
size: db.size(),
|
||||
idle: db.num_idle() as u32,
|
||||
max_connections: db.options().get_max_connections(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_database_detailed(db: &DB) -> DatabaseHealth {
|
||||
let check = check_database_with_latency(db).await;
|
||||
let pool = get_pool_stats(db);
|
||||
|
||||
DatabaseHealth {
|
||||
healthy: check.healthy,
|
||||
latency_ms: check.latency_ms,
|
||||
pool,
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_worker_count(db: &DB) -> i64 {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM worker_ping WHERE ping_at > now() - interval '5 minutes'"
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.unwrap_or(Some(0))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
async fn check_workers_detailed(db: &DB) -> WorkersHealth {
|
||||
let workers = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
worker_group,
|
||||
wm_version
|
||||
FROM worker_ping
|
||||
WHERE ping_at > now() - interval '5 minutes'
|
||||
"#
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let active_count = workers.len() as i64;
|
||||
|
||||
let worker_groups: Vec<String> = workers
|
||||
.iter()
|
||||
.map(|w| w.worker_group.clone())
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let versions: Vec<String> = workers
|
||||
.iter()
|
||||
.map(|w| w.wm_version.clone())
|
||||
.filter(|v| !v.is_empty())
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let min_version = format!(
|
||||
"v{}.{}.{}",
|
||||
MIN_KEEP_ALIVE_VERSION.0, MIN_KEEP_ALIVE_VERSION.1, MIN_KEEP_ALIVE_VERSION.2
|
||||
);
|
||||
|
||||
let healthy = active_count > 0;
|
||||
|
||||
WorkersHealth {
|
||||
healthy,
|
||||
active_count,
|
||||
worker_groups,
|
||||
min_version,
|
||||
versions,
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_queue(db: &DB) -> QueueHealth {
|
||||
let pending_counts = windmill_common::queue::get_queue_counts(db).await;
|
||||
let running_counts = windmill_common::queue::get_queue_running_counts(db).await;
|
||||
|
||||
let pending_jobs: u64 = pending_counts.values().map(|&v| v as u64).sum();
|
||||
let running_jobs: u64 = running_counts.values().map(|&v| v as u64).sum();
|
||||
|
||||
QueueHealth { pending_jobs, running_jobs }
|
||||
}
|
||||
|
||||
fn check_readiness() -> ReadinessHealth {
|
||||
let healthy = IS_READY.load(std::sync::atomic::Ordering::Relaxed);
|
||||
ReadinessHealth { healthy }
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn get_version() -> String {
|
||||
format!("EE {GIT_VERSION}")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
fn get_version() -> String {
|
||||
format!("CE {GIT_VERSION}")
|
||||
}
|
||||
|
||||
// ============ Background Loop ============
|
||||
|
||||
/// Spawn a background task that performs a health check every 10 seconds.
|
||||
/// Updates the cache and prometheus metrics continuously.
|
||||
pub fn start_health_check_loop(
|
||||
db: DB,
|
||||
mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(10));
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = killpill_rx.recv() => {
|
||||
tracing::info!("health check loop shutting down");
|
||||
break;
|
||||
}
|
||||
_ = interval.tick() => {
|
||||
let result = perform_health_check(&db).await;
|
||||
|
||||
log_health_status(&result.response);
|
||||
#[cfg(feature = "prometheus")]
|
||||
update_health_metrics(&result.metrics_data);
|
||||
|
||||
let cached = CachedHealthStatus {
|
||||
status: result.response,
|
||||
cached_at: std::time::Instant::now(),
|
||||
};
|
||||
*STATUS_CACHE.write().await = Some(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============ Handlers ============
|
||||
|
||||
/// Log health status based on severity
|
||||
fn log_health_status(status: &HealthStatusResponse) {
|
||||
if *SILENCE_HEALTH_LOGS {
|
||||
return;
|
||||
}
|
||||
|
||||
match status.status {
|
||||
HealthStatus::Healthy => {
|
||||
tracing::info!(
|
||||
status = "healthy",
|
||||
database_healthy = status.database_healthy,
|
||||
workers_alive = status.workers_alive,
|
||||
checked_at = %status.checked_at,
|
||||
"health check completed"
|
||||
);
|
||||
}
|
||||
HealthStatus::Degraded => {
|
||||
tracing::warn!(
|
||||
status = "degraded",
|
||||
database_healthy = status.database_healthy,
|
||||
workers_alive = status.workers_alive,
|
||||
checked_at = %status.checked_at,
|
||||
"health check: degraded status (no workers alive)"
|
||||
);
|
||||
}
|
||||
HealthStatus::Unhealthy => {
|
||||
tracing::error!(
|
||||
status = "unhealthy",
|
||||
database_healthy = status.database_healthy,
|
||||
workers_alive = status.workers_alive,
|
||||
checked_at = %status.checked_at,
|
||||
"health check: unhealthy status"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data needed for prometheus metrics (internal, not serialized)
|
||||
#[cfg(feature = "prometheus")]
|
||||
struct HealthMetricsData {
|
||||
status: HealthStatus,
|
||||
database_healthy: bool,
|
||||
database_latency_ms: i64,
|
||||
pool_size: u32,
|
||||
pool_idle: u32,
|
||||
pool_max: u32,
|
||||
}
|
||||
|
||||
/// Update prometheus metrics for health status
|
||||
#[cfg(feature = "prometheus")]
|
||||
fn update_health_metrics(data: &HealthMetricsData) {
|
||||
// Update health status phase (only one label is 1, others are 0)
|
||||
if let Some(gauge_vec) = HEALTH_STATUS_PHASE.as_ref() {
|
||||
let (healthy, degraded, unhealthy) = match data.status {
|
||||
HealthStatus::Healthy => (1, 0, 0),
|
||||
HealthStatus::Degraded => (0, 1, 0),
|
||||
HealthStatus::Unhealthy => (0, 0, 1),
|
||||
};
|
||||
gauge_vec.with_label_values(&["healthy"]).set(healthy);
|
||||
gauge_vec.with_label_values(&["degraded"]).set(degraded);
|
||||
gauge_vec.with_label_values(&["unhealthy"]).set(unhealthy);
|
||||
}
|
||||
|
||||
// Database latency
|
||||
if let Some(gauge) = HEALTH_DATABASE_LATENCY.as_ref() {
|
||||
gauge.set(data.database_latency_ms as f64);
|
||||
}
|
||||
|
||||
// Database unresponsive flag
|
||||
if let Some(gauge) = HEALTH_DATABASE_UNRESPONSIVE.as_ref() {
|
||||
gauge.set(if data.database_healthy { 0 } else { 1 });
|
||||
}
|
||||
|
||||
// Pool metrics
|
||||
if let Some(gauge) = HEALTH_DATABASE_POOL_SIZE.as_ref() {
|
||||
gauge.set(data.pool_size as i64);
|
||||
}
|
||||
if let Some(gauge) = HEALTH_DATABASE_POOL_IDLE.as_ref() {
|
||||
gauge.set(data.pool_idle as i64);
|
||||
}
|
||||
if let Some(gauge) = HEALTH_DATABASE_POOL_MAX.as_ref() {
|
||||
gauge.set(data.pool_max as i64);
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of perform_health_check including data needed for metrics
|
||||
struct HealthCheckResult {
|
||||
response: HealthStatusResponse,
|
||||
#[cfg(feature = "prometheus")]
|
||||
metrics_data: HealthMetricsData,
|
||||
}
|
||||
|
||||
/// Perform fresh health check
|
||||
async fn perform_health_check(db: &DB) -> HealthCheckResult {
|
||||
let checked_at = Utc::now();
|
||||
let db_check = check_database_with_latency(db).await;
|
||||
|
||||
let workers_alive = if db_check.healthy {
|
||||
check_worker_count(db).await
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let status = if !db_check.healthy {
|
||||
HealthStatus::Unhealthy
|
||||
} else if workers_alive == 0 {
|
||||
HealthStatus::Degraded
|
||||
} else {
|
||||
HealthStatus::Healthy
|
||||
};
|
||||
|
||||
let response = HealthStatusResponse {
|
||||
status,
|
||||
checked_at,
|
||||
database_healthy: db_check.healthy,
|
||||
workers_alive,
|
||||
};
|
||||
|
||||
#[cfg(feature = "prometheus")]
|
||||
let metrics_data = {
|
||||
let pool_stats = get_pool_stats(db);
|
||||
HealthMetricsData {
|
||||
status,
|
||||
database_healthy: db_check.healthy,
|
||||
database_latency_ms: db_check.latency_ms,
|
||||
pool_size: pool_stats.size,
|
||||
pool_idle: pool_stats.idle,
|
||||
pool_max: pool_stats.max_connections,
|
||||
}
|
||||
};
|
||||
|
||||
HealthCheckResult {
|
||||
response,
|
||||
#[cfg(feature = "prometheus")]
|
||||
metrics_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Status check - cached DB/worker status with optional force refresh
|
||||
async fn health_status(
|
||||
Extension(db): Extension<DB>,
|
||||
Query(query): Query<StatusQuery>,
|
||||
) -> impl IntoResponse {
|
||||
// Check cache (unless force=true)
|
||||
if !query.force {
|
||||
let cache = STATUS_CACHE.read().await;
|
||||
if let Some(cached) = cache.as_ref() {
|
||||
if cached.cached_at.elapsed() < HEALTH_CACHE_TTL {
|
||||
let status_code = if cached.status.status == HealthStatus::Unhealthy {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
} else {
|
||||
StatusCode::OK
|
||||
};
|
||||
return (status_code, Json(cached.status.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss, expired, or force=true - fetch fresh data
|
||||
let health_check_result = perform_health_check(&db).await;
|
||||
|
||||
// Log and update metrics on fresh check
|
||||
log_health_status(&health_check_result.response);
|
||||
#[cfg(feature = "prometheus")]
|
||||
update_health_metrics(&health_check_result.metrics_data);
|
||||
|
||||
// Update cache (clone before acquiring lock to minimize lock duration)
|
||||
let cached = CachedHealthStatus {
|
||||
status: health_check_result.response.clone(),
|
||||
cached_at: std::time::Instant::now(),
|
||||
};
|
||||
{
|
||||
let mut cache = STATUS_CACHE.write().await;
|
||||
*cache = Some(cached);
|
||||
}
|
||||
|
||||
let status_code = if health_check_result.response.status == HealthStatus::Unhealthy {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
} else {
|
||||
StatusCode::OK
|
||||
};
|
||||
|
||||
(status_code, Json(health_check_result.response))
|
||||
}
|
||||
|
||||
/// Detailed health check - requires DB authentication (always fresh, no caching)
|
||||
async fn health_detailed(
|
||||
_authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> impl IntoResponse {
|
||||
let checked_at = Utc::now();
|
||||
let database = check_database_detailed(&db).await;
|
||||
let readiness = check_readiness();
|
||||
|
||||
// Short-circuit if database is down
|
||||
if !database.healthy {
|
||||
let response = DetailedHealthResponse {
|
||||
status: HealthStatus::Unhealthy,
|
||||
checked_at,
|
||||
version: get_version(),
|
||||
checks: HealthChecks {
|
||||
database,
|
||||
workers: None,
|
||||
queue: None,
|
||||
readiness,
|
||||
},
|
||||
};
|
||||
return (StatusCode::SERVICE_UNAVAILABLE, Json(response));
|
||||
}
|
||||
|
||||
let workers = check_workers_detailed(&db).await;
|
||||
let queue = check_queue(&db).await;
|
||||
|
||||
let status = if !workers.healthy {
|
||||
HealthStatus::Degraded
|
||||
} else {
|
||||
HealthStatus::Healthy
|
||||
};
|
||||
|
||||
let response = DetailedHealthResponse {
|
||||
status,
|
||||
checked_at,
|
||||
version: get_version(),
|
||||
checks: HealthChecks {
|
||||
database,
|
||||
workers: Some(workers),
|
||||
queue: Some(queue),
|
||||
readiness,
|
||||
},
|
||||
};
|
||||
|
||||
let status_code = if status == HealthStatus::Unhealthy {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
} else {
|
||||
StatusCode::OK
|
||||
};
|
||||
|
||||
(status_code, Json(response))
|
||||
}
|
||||
@@ -2135,7 +2135,9 @@ 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::<FlowStatus>(flow_status_value.clone()) {
|
||||
if let Ok(flow_status) =
|
||||
serde_json::from_value::<FlowStatus>(flow_status_value.clone())
|
||||
{
|
||||
let trigger_email = flow.email.as_deref().unwrap_or("");
|
||||
conditionally_require_authed_user(Some(authed.clone()), flow_status, trigger_email)?;
|
||||
}
|
||||
@@ -5222,7 +5224,7 @@ async fn add_batch_jobs(
|
||||
|
||||
let tag = if let Some(dedicated_worker) = dedicated_worker {
|
||||
if dedicated_worker && path.is_some() {
|
||||
windmill_common::worker::dedicated_worker_tag(&w_id, &path.clone().unwrap())
|
||||
format!("{}:{}", w_id, path.clone().unwrap())
|
||||
} else {
|
||||
format!("{}", language.as_str())
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@ mod folders;
|
||||
mod granular_acls;
|
||||
mod group_history;
|
||||
mod groups;
|
||||
mod health;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod indexer_ee;
|
||||
mod indexer_oss;
|
||||
@@ -377,10 +376,6 @@ pub async fn run_server(
|
||||
start_all_listeners(db.clone(), &killpill_rx);
|
||||
}
|
||||
|
||||
if server_mode {
|
||||
health::start_health_check_loop(db.clone(), killpill_rx.resubscribe());
|
||||
}
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
.context("binding main windmill server")?;
|
||||
@@ -551,7 +546,6 @@ pub async fn run_server(
|
||||
.nest("/ai", ai::global_service())
|
||||
.nest("/inkeep", inkeep_oss::global_service())
|
||||
.nest("/mcp/w/:workspace_id/list_tools", mcp_list_tools_service)
|
||||
.nest("/health/detailed", health::detailed_service())
|
||||
.route_layer(from_extractor::<ApiAuthed>())
|
||||
.route_layer(from_extractor::<users::Tokened>())
|
||||
// Workspace-scoped OAuth endpoints that don't require authentication
|
||||
@@ -747,7 +741,6 @@ pub async fn run_server(
|
||||
}
|
||||
})
|
||||
.route("/version", get(git_v))
|
||||
.nest("/health/status", health::status_service())
|
||||
.route("/min_keep_alive_version", get(min_keep_alive_version))
|
||||
.route("/uptodate", get(is_up_to_date))
|
||||
.route("/ee_license", get(ee_license))
|
||||
|
||||
@@ -1560,24 +1560,6 @@ 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 = <sha2::Sha256 as sha2::Digest>::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,
|
||||
@@ -1689,7 +1671,7 @@ pub async fn load_worker_config(
|
||||
if let Some(ref dws) = dedicated_workers.as_ref() {
|
||||
let mut dedi_tags: Vec<String> = dws
|
||||
.iter()
|
||||
.map(|dw| dedicated_worker_tag(&dw.workspace_id, &dw.path))
|
||||
.map(|dw| format!("{}:{}", dw.workspace_id, dw.path))
|
||||
.collect();
|
||||
if std::env::var("ADD_FLOW_TAG").is_ok() {
|
||||
dedi_tags.push("flow".to_string());
|
||||
@@ -1697,9 +1679,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![dedicated_worker_tag(
|
||||
&dedicated_worker.workspace_id,
|
||||
&dedicated_worker.path,
|
||||
let mut dedi_tags = vec![format!(
|
||||
"{}:{}",
|
||||
dedicated_worker.workspace_id, dedicated_worker.path
|
||||
)];
|
||||
if std::env::var("ADD_FLOW_TAG").is_ok() {
|
||||
dedi_tags.push("flow".to_string());
|
||||
@@ -2196,58 +2178,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ path = "./src/lib.rs"
|
||||
[features]
|
||||
private = []
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-common/enterprise"]
|
||||
all_sqlx_features = ["enterprise"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -5274,17 +5274,16 @@ async fn push_inner<'c, 'd>(
|
||||
.unwrap_or_else(|| (None, None));
|
||||
|
||||
let tag = if dedicated_worker.is_some_and(|x| x) {
|
||||
let flow_prefix = if job_kind == JobKind::Flow || job_kind == JobKind::FlowDependencies {
|
||||
"flow/"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let full_path = format!(
|
||||
"{}{}",
|
||||
flow_prefix,
|
||||
format!(
|
||||
"{}:{}{}",
|
||||
workspace_id,
|
||||
if job_kind == JobKind::Flow || job_kind == JobKind::FlowDependencies {
|
||||
"flow/"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
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;
|
||||
|
||||
@@ -49,7 +49,6 @@ 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<ParsedResponse, Error> {
|
||||
let bedrock_client = if !api_key.is_empty() {
|
||||
BedrockClient::from_bearer_token(api_key.to_string(), region).await?
|
||||
@@ -59,7 +58,7 @@ impl BedrockQueryBuilder {
|
||||
BedrockClient::from_credentials(
|
||||
access_key_id.to_string(),
|
||||
secret_access_key.to_string(),
|
||||
aws_session_token.map(str::to_string),
|
||||
None,
|
||||
region,
|
||||
)
|
||||
.await?
|
||||
|
||||
@@ -187,13 +187,6 @@ pub struct ProviderResource {
|
||||
deserialize_with = "empty_string_as_none"
|
||||
)]
|
||||
pub aws_secret_access_key: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
#[serde(
|
||||
alias = "awsSessionToken",
|
||||
default,
|
||||
deserialize_with = "empty_string_as_none"
|
||||
)]
|
||||
pub aws_session_token: Option<String>,
|
||||
/// Platform for Anthropic API (standard or google_vertex_ai)
|
||||
#[serde(default)]
|
||||
pub platform: AnthropicPlatform,
|
||||
@@ -239,11 +232,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -686,7 +686,6 @@ 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?
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ pub(crate) async fn update_worker_ping_full(
|
||||
occupancy_rate_30m,
|
||||
} = occupancy_metrics.update_occupancy_metrics();
|
||||
|
||||
let ping_start = std::time::Instant::now();
|
||||
if let Err(e) = (|| {
|
||||
update_worker_ping_full_inner(
|
||||
conn,
|
||||
@@ -82,13 +81,11 @@ pub(crate) async fn update_worker_ping_full(
|
||||
"failed to update worker ping, exiting: {}", e);
|
||||
killpill_tx.send();
|
||||
}
|
||||
let db_latency_ms = ping_start.elapsed().as_millis();
|
||||
tracing::info!(
|
||||
worker = %worker_name, hostname = %hostname,
|
||||
"ping update, memory: container={}MB, windmill={}MB, db_latency={}ms",
|
||||
"ping update, memory: container={}MB, windmill={}MB",
|
||||
memory_usage.unwrap_or_default() / (1024 * 1024),
|
||||
wm_memory_usage.unwrap_or_default() / (1024 * 1024),
|
||||
db_latency_ms
|
||||
wm_memory_usage.unwrap_or_default() / (1024 * 1024)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.633.0";
|
||||
export const VERSION = "v1.631.2";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -77,7 +77,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.633.0";
|
||||
export const VERSION = "1.631.2";
|
||||
|
||||
// Re-exported from constants.ts to maintain backwards compatibility
|
||||
export { WM_FORK_PREFIX } from "./core/constants.ts";
|
||||
|
||||
@@ -54,9 +54,6 @@ 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 && \
|
||||
|
||||
@@ -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 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 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
|
||||
@@ -86,7 +86,4 @@ 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
|
||||
|
||||
@@ -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 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 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
|
||||
@@ -86,7 +86,4 @@ 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
|
||||
|
||||
16
flake.lock
generated
16
flake.lock
generated
@@ -33,6 +33,21 @@
|
||||
"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,
|
||||
@@ -68,6 +83,7 @@
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-claude": "nixpkgs-claude",
|
||||
"nixpkgs-oapi-gen": "nixpkgs-oapi-gen",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
|
||||
10
flake.nix
10
flake.nix
@@ -3,10 +3,12 @@
|
||||
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, flake-utils, rust-overlay
|
||||
outputs = { self, nixpkgs, nixpkgs-claude, flake-utils, rust-overlay
|
||||
, nixpkgs-oapi-gen }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
@@ -15,6 +17,10 @@
|
||||
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;
|
||||
@@ -135,6 +141,8 @@
|
||||
|
||||
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; [
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Core Principles
|
||||
|
||||
- Follow the `svelte-frontend` skill for best practices: .claude/skills/svelte-frontend/SKILL.md
|
||||
- Follow @svelte5-best-practices.mdc for detailed guidelines
|
||||
- Use Runes ($state, $derived, $effect) for reactivity
|
||||
- Keep components small and focused
|
||||
- Always use keys in {#each} blocks
|
||||
@@ -93,14 +93,6 @@ 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
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.633.0",
|
||||
"version": "1.631.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.633.0",
|
||||
"version": "1.631.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user