Compare commits
2 Commits
rf/whitela
...
di/ee-refa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22c495e139 | ||
|
|
ee1c7c300c |
@@ -47,7 +47,6 @@ Windmill uses a workspace-based architecture with multiple crates:
|
||||
- Group related routes together
|
||||
- Use consistent response formats (JSON)
|
||||
- Follow proper authentication and authorization patterns
|
||||
- Do not forget to update backend/windmill-api/openapi.yaml after modifying an api endpoint
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
|
||||
2
.github/workflows/aider-after-review.yaml
vendored
2
.github/workflows/aider-after-review.yaml
vendored
@@ -90,5 +90,5 @@ jobs:
|
||||
with:
|
||||
needs_processing: false
|
||||
base_prompt: ${{ needs.check-and-prepare.outputs.prompt_content }}
|
||||
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
|
||||
rules_files: "CLAUDE.md backend/CLAUDE.md frontend/CLAUDE.md"
|
||||
secrets: inherit
|
||||
|
||||
2
.github/workflows/aider-external.yaml
vendored
2
.github/workflows/aider-external.yaml
vendored
@@ -76,5 +76,5 @@ jobs:
|
||||
issue_body: ${{ needs.check-and-prepare.outputs.issue_body }}
|
||||
instruction: ${{ needs.check-and-prepare.outputs.instruction }}
|
||||
issue_id: ${{ github.event.client_payload.issue_id }}
|
||||
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
|
||||
rules_files: "CLAUDE.md backend/CLAUDE.md frontend/CLAUDE.md"
|
||||
secrets: inherit
|
||||
|
||||
2
.github/workflows/aider.yaml
vendored
2
.github/workflows/aider.yaml
vendored
@@ -161,5 +161,5 @@ jobs:
|
||||
issue_body: ${{ needs.check-and-prepare.outputs.issue_body }}
|
||||
instruction: ${{ needs.check-and-prepare.outputs.comment_content }}
|
||||
issue_id: ${{ github.event.issue.number }}
|
||||
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
|
||||
rules_files: "CLAUDE.md backend/CLAUDE.md frontend/CLAUDE.md"
|
||||
secrets: inherit
|
||||
|
||||
2
.github/workflows/backend-check.yml
vendored
2
.github/workflows/backend-check.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
timeout-minutes: 16
|
||||
run: |
|
||||
mkdir -p fake_frontend_build
|
||||
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh)
|
||||
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features
|
||||
|
||||
check_ee:
|
||||
runs-on: ubicloud-standard-8
|
||||
|
||||
4
.github/workflows/build-publish-rh-image.yml
vendored
4
.github/workflows/build-publish-rh-image.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
|
||||
2
.github/workflows/build_windows_worker_.yml
vendored
2
.github/workflows/build_windows_worker_.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages_windows,mcp,private
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,mcp
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
8
.github/workflows/claude.yml
vendored
8
.github/workflows/claude.yml
vendored
@@ -69,17 +69,17 @@ jobs:
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
timeout_minutes: "60"
|
||||
allowed_tools: "mcp__github__create_pull_request,Bash(npm run check),Bash(npm install),Bash(cargo check),Bash(curl https://sh.rustup.rs -sSf | sh -s -- -y)"
|
||||
allowed_tools: "mcp__github__create_pull_request,Bash(npm run check),Bash(npm install),Bash(cargo check),Bash(curl https://sh.rustup.rs -sSf | sh)"
|
||||
custom_instructions: "IMPORTANT INSTRUCTIONS:
|
||||
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main, with the title starting with [Claude PR].
|
||||
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a PR from that branch to main, with the title starting with [Claude PR].
|
||||
- If you made changes to the frontend code, run npm install, then npm run generate-backend-client, then npm run check. You can ignore warnings that are reported by the check script, but fix the errors.
|
||||
- If you made changes to the backend code, install Rust and then run cargo check. You can ignore warnings that are reported by the check script, but fix the errors.
|
||||
- DO NOT FORGET TO OPEN A DRAFT PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
|
||||
- DO NOT FORGET TO OPEN A PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
|
||||
AVAILABLE TOOLS:
|
||||
- mcp__github__create_pull_request: Create a PR from a branch to main
|
||||
- Bash(npm run check): Run the check script. You should run this tool after making changes to the frontend code.
|
||||
- Bash(npm install): Install dependencies. You need this to run npm run check.
|
||||
- Bash(npm run generate-backend-client): Generate the backend client. You need this to run npm run check.
|
||||
- Bash(cargo check): Run the cargo check script. You should run this tool after making changes to the backend code.
|
||||
- Bash(curl https://sh.rustup.rs -sSf | sh -s -- -y): Install Rust. You need this to run cargo check."
|
||||
- Bash(curl https://sh.rustup.rs -sSf | sh): Install Rust. You need this to run cargo check."
|
||||
trigger_phrase: "/ai"
|
||||
|
||||
3
.github/workflows/discord-notification.yml
vendored
3
.github/workflows/discord-notification.yml
vendored
@@ -17,11 +17,8 @@ jobs:
|
||||
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
PR_STATUS: "opened"
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
DISCORD_CHANNEL_ID: "1372204995868491786"
|
||||
DISCORD_GUILD_ID: "930051556043276338"
|
||||
secrets:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_PR_REVIEWS_WEBHOOK }}
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
|
||||
|
||||
merge_success_emoji:
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
4
.github/workflows/docker-image.yml
vendored
4
.github/workflows/docker-image.yml
vendored
@@ -92,7 +92,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp,private
|
||||
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,private,deno_core,mcp
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
|
||||
4
.github/workflows/helmchart_on_release.yml
vendored
4
.github/workflows/helmchart_on_release.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: windmill-labs/windmill-helm-charts
|
||||
token: ${{ secrets.HELM_CHART_TOKEN }}
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
|
||||
- name: Create PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HELM_CHART_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.DOCS_TOKEN }}
|
||||
run: |
|
||||
gh pr create \
|
||||
--title "helm: bump version to ${{ env.VERSION }}" \
|
||||
|
||||
2
.github/workflows/publish_windows_worker.yml
vendored
2
.github/workflows/publish_windows_worker.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages_windows,mcp,private
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,mcp
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
@@ -38,45 +38,24 @@ jobs:
|
||||
- name: Send Discord notification and start a thread
|
||||
env:
|
||||
WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
|
||||
CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }}
|
||||
GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }}
|
||||
PR_TITLE: ${{ inputs.PR_TITLE }}
|
||||
PR_NUMBER: ${{ inputs.PR_NUMBER }}
|
||||
PR_URL: ${{ inputs.PR_URL }}
|
||||
PR_AUTHOR: ${{ inputs.PR_AUTHOR }}
|
||||
run: |
|
||||
# Check if thread already exists
|
||||
thread_exists=false
|
||||
if threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" "https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active"); then
|
||||
if thread_id=$(echo "$threads" | jq -r --arg cid "$CHANNEL_ID" --arg pref "#${PR_NUMBER}:" '.threads[] | select(.parent_id == $cid and (.name | startswith($pref))) | .id' 2>/dev/null); then
|
||||
if [ -n "$thread_id" ]; then
|
||||
thread_exists=true
|
||||
echo "Thread already exists, skipping creation"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Failed to check for existing threads, will create new thread"
|
||||
fi
|
||||
|
||||
# Create thread if it doesn't exist or if check failed
|
||||
if [ "$thread_exists" = false ]; then
|
||||
echo "Creating new thread"
|
||||
THREAD_TITLE="#${PR_NUMBER}: ${PR_TITLE} by \`${PR_AUTHOR}\`"
|
||||
payload=$(jq -n \
|
||||
--arg content "${PR_URL}" \
|
||||
--arg thread "${THREAD_TITLE:0:99}" \
|
||||
'{
|
||||
content: $content,
|
||||
thread_name: $thread,
|
||||
auto_archive_duration: 10080
|
||||
}'
|
||||
)
|
||||
curl -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "$payload" \
|
||||
"$WEBHOOK_URL"
|
||||
fi
|
||||
payload=$(jq -n \
|
||||
--arg content "${PR_URL}" \
|
||||
--arg thread "#${PR_NUMBER}: $PR_TITLE by \`${PR_AUTHOR}\`" \
|
||||
'{
|
||||
content: $content,
|
||||
thread_name: $thread,
|
||||
auto_archive_duration: 10080
|
||||
}'
|
||||
)
|
||||
curl -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "$payload" \
|
||||
"$WEBHOOK_URL"
|
||||
|
||||
merge_success_emoji:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
34
.github/workflows/validate-openapi.yml
vendored
34
.github/workflows/validate-openapi.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Validate OpenAPI Spec
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'backend/windmill-api/openapi*'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'backend/windmill-api/openapi*'
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install openapi-generator-cli
|
||||
run: npm install @openapitools/openapi-generator-cli -g
|
||||
|
||||
- name: Validate openapi.yaml
|
||||
run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi.yaml
|
||||
|
||||
- name: Validate openapi-deref.json
|
||||
run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi-deref.json
|
||||
|
||||
# Does not work well with dereferenced yaml
|
||||
# - name: Validate openapi-deref.yaml
|
||||
# run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi-deref.yaml
|
||||
|
||||
89
CHANGELOG.md
89
CHANGELOG.md
@@ -1,94 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.496.3](https://github.com/windmill-labs/windmill/compare/v1.496.2...v1.496.3) (2025-06-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve concurrent job parallelism performance ([e8836a3](https://github.com/windmill-labs/windmill/commit/e8836a393a872bb91e68ba0037681caf24149470))
|
||||
* Prioritize diff contexts in script mode for ai chat ([#5888](https://github.com/windmill-labs/windmill/issues/5888)) ([a47939d](https://github.com/windmill-labs/windmill/commit/a47939d13c30e2d4b41efd539f845959174d4fb1))
|
||||
|
||||
## [1.496.2](https://github.com/windmill-labs/windmill/compare/v1.496.1...v1.496.2) (2025-06-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add clearable by default for select ([#5900](https://github.com/windmill-labs/windmill/issues/5900)) ([b44b9c1](https://github.com/windmill-labs/windmill/commit/b44b9c1b82116ad5487af95d1f78226d56c75179))
|
||||
|
||||
## [1.496.1](https://github.com/windmill-labs/windmill/compare/v1.496.0...v1.496.1) (2025-06-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* never consider minor version for global site packages ([#5893](https://github.com/windmill-labs/windmill/issues/5893)) ([22b2f49](https://github.com/windmill-labs/windmill/commit/22b2f4988db9314f2403508933d0aa932187c668))
|
||||
|
||||
## [1.496.0](https://github.com/windmill-labs/windmill/compare/v1.495.1...v1.496.0) (2025-06-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* generate http route triggers from openapi spec ([#5857](https://github.com/windmill-labs/windmill/issues/5857)) ([5713483](https://github.com/windmill-labs/windmill/commit/571348377b73d54b4d2a1c5775ab00b247b01910))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow fileupload drag and drop in edit mode on full component without triggering file picker ([#5889](https://github.com/windmill-labs/windmill/issues/5889)) ([9ae3212](https://github.com/windmill-labs/windmill/commit/9ae3212a1e0f88a8297bf41ab53e3c1be4bcc56c))
|
||||
* **python:** account instance version when cli deploy and local lockfile ([#5894](https://github.com/windmill-labs/windmill/issues/5894)) ([ec552d5](https://github.com/windmill-labs/windmill/commit/ec552d5ef6fdb5e824e453f196f9cf16629ee2ea))
|
||||
* use full client side js library for route gen from openapi ([#5891](https://github.com/windmill-labs/windmill/issues/5891)) ([3c3fdbd](https://github.com/windmill-labs/windmill/commit/3c3fdbdf26a9581b815210839b91ebdedb924093))
|
||||
|
||||
## [1.495.0](https://github.com/windmill-labs/windmill/compare/v1.494.0...v1.495.0) (2025-06-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add ask mode to AI chat ([#5878](https://github.com/windmill-labs/windmill/issues/5878)) ([67ab469](https://github.com/windmill-labs/windmill/commit/67ab46990ad0c9fad810a64c54297419c6151c79))
|
||||
* add navigator mode to AIChat and unify UI ([#5859](https://github.com/windmill-labs/windmill/issues/5859)) ([cbba829](https://github.com/windmill-labs/windmill/commit/cbba8297cd4c1caa21b96a8422bbbd5c306b8398))
|
||||
* ai flow chat ([#5842](https://github.com/windmill-labs/windmill/issues/5842)) ([68ebf66](https://github.com/windmill-labs/windmill/commit/68ebf667d5c0bc306329d0b55a3cc59e5b4862cb))
|
||||
* ai prompts improvements + o3/o4 support ([#5862](https://github.com/windmill-labs/windmill/issues/5862)) ([825422c](https://github.com/windmill-labs/windmill/commit/825422c48456b2c9b230e1a35914b3fbf7d1e836))
|
||||
* connect fix btn in flow editor to ai chat ([#5863](https://github.com/windmill-labs/windmill/issues/5863)) ([6247d15](https://github.com/windmill-labs/windmill/commit/6247d159ce25ae13f6fbc5c105df88305ce29451))
|
||||
* fix backward compatibility pg 14 for postgres trigger ([#5851](https://github.com/windmill-labs/windmill/issues/5851)) ([4cbcbdb](https://github.com/windmill-labs/windmill/commit/4cbcbdb960b469acf773d3943128b6c7d0dcb0b8))
|
||||
* ssh repl like direct to workers hosts machine ([#5809](https://github.com/windmill-labs/windmill/issues/5809)) ([f252657](https://github.com/windmill-labs/windmill/commit/f2526571a3614156b2b1e5cc91b15d0c57565d99))
|
||||
* use rust-postgres client instead of sqlx for postgres trigger ([#5853](https://github.com/windmill-labs/windmill/issues/5853)) ([39dbd64](https://github.com/windmill-labs/windmill/commit/39dbd646b9683e0ad8de047cca786ae468759e77))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* broken event dispatch for simpleditor ([#5879](https://github.com/windmill-labs/windmill/issues/5879)) ([df4992a](https://github.com/windmill-labs/windmill/commit/df4992a9295ed188c2a2cb0a5dfd3e33ae2e2dcb))
|
||||
* cannot parse INSTANCE_PYTHON_VERSION ([#5874](https://github.com/windmill-labs/windmill/issues/5874)) ([a0b302d](https://github.com/windmill-labs/windmill/commit/a0b302d2c58d4245260376cf280bc866be91717c))
|
||||
* fix regex that extract workspaces from custom tags ([#5876](https://github.com/windmill-labs/windmill/issues/5876)) ([1551dc8](https://github.com/windmill-labs/windmill/commit/1551dc8af22f6ea41f68290ace4c58f936c47745))
|
||||
* nit ai flow prompt ([#5867](https://github.com/windmill-labs/windmill/issues/5867)) ([3e769f0](https://github.com/windmill-labs/windmill/commit/3e769f0c591b80138b3a356d147228675756452f))
|
||||
* **python:** assign PATCH version to python runtime only when needed ([#5866](https://github.com/windmill-labs/windmill/issues/5866)) ([50a5c1f](https://github.com/windmill-labs/windmill/commit/50a5c1f56a7e45882fa0095203de709571e149bb))
|
||||
* remove duplicate tools from script ai chat ([#5880](https://github.com/windmill-labs/windmill/issues/5880)) ([fe4a767](https://github.com/windmill-labs/windmill/commit/fe4a767df0e6f46fd0c0fd21b4116c7375978bf9))
|
||||
* replace crypto.randomUUID with generateRandomString for HTTP compatibility ([#5849](https://github.com/windmill-labs/windmill/issues/5849)) ([64f35d0](https://github.com/windmill-labs/windmill/commit/64f35d050fb0d1008ce7142fd62d500845e62c4a)), closes [#5847](https://github.com/windmill-labs/windmill/issues/5847)
|
||||
|
||||
## [1.494.0](https://github.com/windmill-labs/windmill/compare/v1.493.4...v1.494.0) (2025-05-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* array of s3 objects in input maker ([806d669](https://github.com/windmill-labs/windmill/commit/806d66972568d21a1621acd1b30db5ae9b217341))
|
||||
* **rust:** shared build directory ([#5610](https://github.com/windmill-labs/windmill/issues/5610)) ([ed61d97](https://github.com/windmill-labs/windmill/commit/ed61d9770031c1a04908880dbd3e5fb692df9946))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow disable tabs for sidebar/accordion tabs ([#5838](https://github.com/windmill-labs/windmill/issues/5838)) ([80277d1](https://github.com/windmill-labs/windmill/commit/80277d14d02e8e596c7002326946142226d382a6))
|
||||
|
||||
## [1.493.4](https://github.com/windmill-labs/windmill/compare/v1.493.3...v1.493.4) (2025-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* templatev2 delete issue ([#5834](https://github.com/windmill-labs/windmill/issues/5834)) ([ed3ad32](https://github.com/windmill-labs/windmill/commit/ed3ad327a235c16b9f3aa7f8edeefe61b0c01da3))
|
||||
|
||||
## [1.493.3](https://github.com/windmill-labs/windmill/compare/v1.493.2...v1.493.3) (2025-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* evalv2 prohibit component delete ([e302aa3](https://github.com/windmill-labs/windmill/commit/e302aa38b5977dd406ae05e1d8dbb74cb7dc3d17))
|
||||
* faster layout for larger graphs ([8d12bcc](https://github.com/windmill-labs/windmill/commit/8d12bcc8ee2991909ea0d9bb57f04f0d4106c69f))
|
||||
|
||||
## [1.493.2](https://github.com/windmill-labs/windmill/compare/v1.493.1...v1.493.2) (2025-05-28)
|
||||
|
||||
|
||||
|
||||
74
CLAUDE.md
74
CLAUDE.md
@@ -1,3 +1,71 @@
|
||||
To have an overview of what this app does, see @.cursor/rules/windmill-overview.mdc
|
||||
For backend modifications, follow the rules mentioned here @.cursor/rules/rust-best-practices.mdc
|
||||
For frontend modifications, follow the rules mentioned here @.cursor/rules/svelte5-best-practices.mdc
|
||||
# Windmill Overview
|
||||
|
||||
Windmill is an open-source developer platform for building internal tools, API integrations, background jobs, workflows, and user interfaces. It offers a unified system where scripts are automatically turned into sharable UIs and can be composed into flows or embedded in custom applications.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
- **Script Development and Execution**: Write and run scripts in Python, TypeScript/JavaScript (Deno/Bun), Go, Bash, SQL, and other languages
|
||||
- **Workflow Orchestration**: Compose scripts into multi-step flows with conditional logic, loops, and error handling
|
||||
- **UI Generation**: Automatically generate UIs from scripts or build custom applications with a low-code editor
|
||||
- **Job Scheduling**: Trigger scripts and flows on schedules, webhooks, or external events
|
||||
- **Resource Management**: Securely store and use credentials, databases, and other connections
|
||||
|
||||
## Platform Architecture
|
||||
|
||||
The Windmill platform consists of several key components:
|
||||
|
||||
- **Frontend UI**: Web-based interface for script and flow development, app building, and result visualization
|
||||
- **API Server**: Central API that handles authentication, resource management, and job coordination
|
||||
- **Workers**: Execute scripts in their respective environments with proper sandboxing
|
||||
- **Database**: PostgreSQL database for storage of scripts, flows, resources, job results, and more
|
||||
- **Job Queue**: Queue system for managing job execution, implemented in PostgreSQL
|
||||
- **Client Libraries**: Libraries for interacting with Windmill from Python, TypeScript, or command line
|
||||
|
||||
# Windmill Backend Architecture
|
||||
|
||||
The Windmill backend is written in Rust and consists of several services working together. These services are designed for horizontal scaling with stateless API servers and workers that can be deployed across multiple machines.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **API Server (`windmill-api`)**: Handles HTTP requests, authentication, and resource management
|
||||
- **Queue Manager (`windmill-queue`)**: Manages the job queue in PostgreSQL
|
||||
- **Worker System (`windmill-worker`)**: Executes jobs in sandboxed environments
|
||||
- **Common Utilities (`windmill-common`)**: Shared code used by multiple services
|
||||
- **Git Sync (`windmill-git-sync`)**: Synchronizes scripts with Git repositories
|
||||
|
||||
## Job Execution System
|
||||
|
||||
The job execution process follows these steps:
|
||||
|
||||
1. The API server receives a request to run a script or flow and creates a job record in the database
|
||||
2. The job is added to the queue system in PostgreSQL
|
||||
3. Workers continuously poll the queue for jobs matching their capabilities
|
||||
4. When a job is picked up, it's routed to the appropriate language executor
|
||||
5. The script is executed in a sandboxed environment using NSJAIL for security
|
||||
6. Results are processed and stored in the database
|
||||
7. For flows, each step creates a new job that goes through the same process
|
||||
|
||||
Windmill supports worker tags and groups to route jobs to workers with specific capabilities or resource access.
|
||||
|
||||
# Windmill Frontend Architecture
|
||||
|
||||
The Windmill frontend is built with Svelte and provides several key interfaces for interacting with the platform.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **Script Builder**: Code editor with language support, schema inference, and dependency management
|
||||
- **Flow Builder**: Visual editor for creating multi-step workflows with branching and looping
|
||||
- **App Editor**: Grid-based editor for building custom UIs that integrate scripts and flows
|
||||
- **Schema Form System**: Generates form interfaces from script parameters automatically
|
||||
- **Result Viewer**: Visualizes job results, logs, and execution status
|
||||
|
||||
The frontend uses the Monaco editor (same as VS Code) for code editing, with specialized language support for all supported script languages.
|
||||
|
||||
## UI Framework
|
||||
|
||||
The frontend is built with Svelte, providing a reactive and component-based architecture. Key frontend technologies include:
|
||||
|
||||
- **Svelte/SvelteKit**: Core framework for UI components and routing
|
||||
- **Monaco Editor**: Code editing experience similar to VS Code
|
||||
- **Schema Form**: Automatic UI generation from TypeScript/JSON schemas
|
||||
- **Tailwind CSS**: Utility-first CSS framework for styling
|
||||
|
||||
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@@ -6,5 +6,4 @@ tracing.folded
|
||||
heaptrack*
|
||||
index/
|
||||
windmill-api/openapi-*.*
|
||||
.duckdb/*
|
||||
*ee.rs
|
||||
.duckdb/*
|
||||
@@ -1 +0,0 @@
|
||||
!*ee.rs
|
||||
@@ -61,8 +61,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM sqs_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74"
|
||||
}
|
||||
15
backend/.sqlx/query-1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa.json
generated
Normal file
15
backend/.sqlx/query-1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2bf5f7f2cf9d85a5d23e5db2f7616fb41fece9b3d46fde2d546d70b46f9008e3"
|
||||
}
|
||||
22
backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json
generated
Normal file
22
backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "usage",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31"
|
||||
}
|
||||
20
backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json
generated
Normal file
20
backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SHOW WAL_LEVEL;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "wal_level",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48"
|
||||
}
|
||||
20
backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json
generated
Normal file
20
backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT pubname AS publication_name FROM pg_publication;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "publication_name",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45"
|
||||
}
|
||||
@@ -70,8 +70,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n tag, \n script_lang AS \"script_lang!: _\"\n FROM \n v2_job\n WHERE \n id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "script_lang!: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4e5273b9ce05f6ee2dfd5f14c8574a0cf43682480452f7dbe23012320fe7fe25"
|
||||
}
|
||||
26
backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json
generated
Normal file
26
backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n slot_name,\n active\n FROM\n pg_replication_slots \n WHERE \n plugin = 'pgoutput' AND\n slot_type = 'logical';\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "slot_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13"
|
||||
}
|
||||
15
backend/.sqlx/query-553108ba3c0b8d579800bc8b5a4f887d79fb4c13b60b19c4913a8db18521958c.json
generated
Normal file
15
backend/.sqlx/query-553108ba3c0b8d579800bc8b5a4f887d79fb4c13b60b19c4913a8db18521958c.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE parent_job = $1\n AND f.id = j.id AND q.id = j.id\n AND suspend = $2 AND (f.flow_status->'step')::int = 0",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "553108ba3c0b8d579800bc8b5a4f887d79fb4c13b60b19c4913a8db18521958c"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "usage",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "56b2326015fde12b1a4efa226518566101dd27a0f3363884781071d417f8b7e7"
|
||||
}
|
||||
@@ -61,8 +61,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
22
backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json
generated
Normal file
22
backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "usage",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM gcp_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "6a19c440a7a8064f3969cf6f48adea0bfdb683de9555e374ce5731e0b3c379f9"
|
||||
}
|
||||
@@ -147,8 +147,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
22
backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json
generated
Normal file
22
backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT slot_name FROM pg_replication_slots where slot_name = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "slot_name",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396"
|
||||
}
|
||||
22
backend/.sqlx/query-7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2.json
generated
Normal file
22
backend/.sqlx/query-7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n active_pid \n FROM \n pg_replication_slots \n WHERE \n slot_name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "active_pid",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2"
|
||||
}
|
||||
@@ -34,8 +34,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "usage",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "83f64dd93b1ddc03b84681d65d9be69959987cbac1d83b64225fd1bf9ab047c9"
|
||||
}
|
||||
40
backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json
generated
Normal file
40
backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n puballtables AS all_table,\n pubinsert AS insert,\n pubupdate AS update,\n pubdelete AS delete\n FROM\n pg_publication\n WHERE\n pubname = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "all_table",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "insert",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "update",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "delete",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE worker_ping SET\nping_at = now(),\njobs_executed = 1,\ncurrent_job_id = $1,\ncurrent_job_workspace_id = 'admins'\nWHERE worker = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "997586ac14384db2c0eeee1bb3382cc6ae013695d0cda6da9ab848ca1b9a9606"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM mqtt_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a8b470b463ca4b7c00c7ef6e9f36c23f8bbcefc288a56d61122bfd6fe5ca7e8d"
|
||||
}
|
||||
@@ -36,8 +36,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
22
backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json
generated
Normal file
22
backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT pubname FROM pg_publication WHERE pubname = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "pubname",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60"
|
||||
}
|
||||
15
backend/.sqlx/query-c3025cdb6e421e1225d420e8b1efd18d1dd3bb2fac53c1f2df648b61fb7488aa.json
generated
Normal file
15
backend/.sqlx/query-c3025cdb6e421e1225d420e8b1efd18d1dd3bb2fac53c1f2df648b61fb7488aa.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE worker_ping SET \nping_at = now(), \njobs_executed = 1, \ncurrent_job_id = $1, \ncurrent_job_workspace_id = 'admins' \nWHERE worker = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c3025cdb6e421e1225d420e8b1efd18d1dd3bb2fac53c1f2df648b61fb7488aa"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8"
|
||||
}
|
||||
@@ -70,8 +70,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
15
backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json
generated
Normal file
15
backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE q.workspace_id = $1 AND q.suspend = $3 AND j.parent_job = $2\n AND f.id = j.id AND q.id = j.id\n AND (f.flow_status->'step')::int = 0",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632"
|
||||
}
|
||||
@@ -147,8 +147,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path FROM script WHERE workspace_id = $1 AND archived = false",
|
||||
"query": "SELECT tag FROM v2_job WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0"
|
||||
"hash": "faf2c77242e0ab39b33886edf3b742531bf1351d0be1c3631bde0adfe375497a"
|
||||
}
|
||||
40
backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json
generated
Normal file
40
backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n schemaname AS schema_name,\n tablename AS table_name,\n CASE\n WHEN array_length(attnames, 1) = (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = pg_publication_tables.schemaname AND table_name = pg_publication_tables.tablename)\n THEN NULL\n ELSE attnames\n END AS columns,\n rowfilter AS where_clause\n FROM\n pg_publication_tables\n WHERE\n pubname = $1;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "schema_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "table_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "columns",
|
||||
"type_info": "NameArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "where_clause",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154"
|
||||
}
|
||||
@@ -65,8 +65,7 @@
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb"
|
||||
"java"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
104
backend/CLAUDE.md
Normal file
104
backend/CLAUDE.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Windmill Backend - Rust Best Practices
|
||||
|
||||
## Project Structure
|
||||
|
||||
Windmill uses a workspace-based architecture with multiple crates:
|
||||
|
||||
- **windmill-api**: API server functionality
|
||||
- **windmill-worker**: Job execution
|
||||
- **windmill-common**: Shared code used by all crates
|
||||
- **windmill-queue**: Job & flow queuing
|
||||
- **windmill-audit**: Audit logging
|
||||
- Other specialized crates (git-sync, autoscaling, etc.)
|
||||
|
||||
## Adding New Code
|
||||
|
||||
### Module Organization
|
||||
|
||||
- Place new code in the appropriate crate based on functionality
|
||||
- For API endpoints, create or modify files in `windmill-api/src/` organized by domain
|
||||
- For shared functionality, use `windmill-common/src/`
|
||||
- Use the `_ee.rs` suffix for enterprise-only modules
|
||||
- Follow existing patterns for file structure and organization
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use the custom `Error` enum from `windmill-common::error`
|
||||
- Return `Result<T, Error>` or `JsonResult<T>` for functions that can fail
|
||||
- Use the `?` operator for error propagation
|
||||
- Add location tracking to errors using `#[track_caller]`
|
||||
|
||||
### Database Operations
|
||||
|
||||
- Use `sqlx` for database operations with prepared statements
|
||||
- Leverage existing database helper functions in `db.rs` modules
|
||||
- Use transactions for multi-step operations
|
||||
- Handle database errors properly
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- Follow existing patterns in the `windmill-api` crate
|
||||
- Use axum's routing system and extractors
|
||||
- Group related routes together
|
||||
- Use consistent response formats (JSON)
|
||||
- Follow proper authentication and authorization patterns
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
When generating code, especially involving `serde`, `sqlx`, and `tokio`, prioritize performance by applying the following principles:
|
||||
|
||||
### Serde Optimizations (Serialization & Deserialization)
|
||||
|
||||
- **Specify Structure Explicitly:** When defining structs for Serde (`#[derive(Serialize, Deserialize)]`), use `#[serde(...` attributes extensively. This includes:
|
||||
- `#[serde(rename = "...")]` or `#[serde(alias = "...")]` to map external names precisely, avoiding dynamic lookups.
|
||||
- `#[serde(default)]` for optional fields with default values, reducing parsing complexity.
|
||||
- `#[serde(skip_serializing_if = "...")]` to avoid writing fields that meet a certain condition (e.g., `Option::is_none()`, `Vec::is_empty()`, or a custom function), reducing output size and serialization work.
|
||||
- `#[serde(skip_serializing)]` or `#[serde(skip_deserializing)]` for fields that should _not_ be included.
|
||||
- **Prefer Borrowing:** Where possible and safe (data lifetime allows), use `Cow<'a, str>` or `&'a str` (with `#[serde(borrow)]`) instead of `String` for string fields during deserialization. This avoids allocating new strings, enabling zero-copy reading from the input buffer. Apply this principle to byte slices (`&'a [u8]` / `Cow<'a, [u8]>`) and potentially borrowed vectors as well.
|
||||
- **Avoid Intermediate `Value`:** Unless the data structure is truly dynamic or unknown at compile time, deserialize directly into a well-defined struct or enum rather than into `serde_json::Value` (or equivalent for other formats). This avoids unnecessary heap allocations and type switching.
|
||||
|
||||
### SQLx Optimizations (Database Interaction)
|
||||
|
||||
- **Select Only Necessary Columns:** In `SELECT` queries, list specific column names rather than using `SELECT *`. This reduces data transferred from the database and the work needed for hydration/deserialization.
|
||||
- **Batch Operations:** For multiple `INSERT`, `UPDATE`, or `DELETE` statements, prefer executing them in a single query if the database and driver support it efficiently (e.g., `INSERT INTO ... VALUES (...), (...), ...`). This minimizes round trips to the database.
|
||||
- **Avoid N+1 Queries:** Do not loop through results of one query and execute a separate query for each item (e.g., fetching users, then querying for each user's profile in a loop). Instead, use JOINs or a single query with an `IN` clause to fetch related data efficiently.
|
||||
- **Deserialize Directly:** Use `#[derive(FromRow)]` on structs and ensure the struct fields match the selected columns in the query. This allows SQLx to hydrate objects directly, avoiding intermediate data structures.
|
||||
- **Parameterize Queries:** Always use SQLx's query methods (`.bind(...)`) to pass values as parameters rather than string formatting. This prevents SQL injection and allows the database to cache query plans, improving performance on repeated executions.
|
||||
|
||||
### Tokio Optimizations (Asynchronous Runtime)
|
||||
|
||||
- **Avoid Blocking Operations:** **Crucially**, never perform blocking operations (synchronous file I/O, `std::thread::sleep`, CPU-bound loops, `std::sync::Mutex::lock`, blocking network calls without `tokio::net`) directly within an `async fn` or a standard `tokio::spawn` task. Blocking pauses the entire worker thread, potentially starving other tasks. Use `tokio::task::spawn_blocking` for CPU-intensive work or blocking I/O.
|
||||
- **Use Tokio's Async Primitives:** Prefer `tokio::sync` (channels, mutexes, semaphores), `tokio::io`, `tokio::net`, and `tokio::time` over their `std` counterparts in asynchronous contexts. These are designed to yield control back to the scheduler.
|
||||
- **Manage Concurrency:** Be mindful of how many tasks are spawned. Creating a new task for every tiny piece of work can introduce overhead. Group related asynchronous operations where appropriate.
|
||||
- **Handle Shared State Efficiently:** Use `Arc` for shared ownership in concurrent tasks. When shared state needs mutation, prefer `tokio::sync::Mutex` over `std::sync::Mutex` in `async` code. Consider `tokio::sync::RwLock` if reads significantly outnumber writes. Minimize the duration for which locks are held.
|
||||
- **Understand `.await`:** Place `.await` strategically to allow the runtime to switch to other ready tasks. Ensure that `.await` points to genuinely asynchronous operations.
|
||||
- **Backpressure:** If dealing with data streams or queues between tasks, implement backpressure mechanisms (e.g., bounded channels like `tokio::sync::mpsc::channel`) to prevent one component from overwhelming another or critical resources like the database.
|
||||
|
||||
## Enterprise Features
|
||||
|
||||
- Use feature flags for enterprise functionality
|
||||
- Conditionally compile with `#[cfg(feature = "enterprise")]`
|
||||
- Isolate enterprise code in separate modules
|
||||
|
||||
## Code Style
|
||||
|
||||
- Group imports by external and internal crates
|
||||
- Place struct/enum definitions before implementations
|
||||
- Group similar functionality together
|
||||
- Use descriptive naming consistent with the codebase
|
||||
- Follow existing patterns for async code using tokio
|
||||
|
||||
## Testing
|
||||
|
||||
- Write unit tests for core functionality
|
||||
- Use the `#[cfg(test)]` module for test code
|
||||
- For database tests, use the existing test utilities
|
||||
|
||||
## Common Crates Used
|
||||
|
||||
- **tokio**: For async runtime
|
||||
- **axum**: For web server and routing
|
||||
- **sqlx**: For database operations
|
||||
- **serde**: For serialization/deserialization
|
||||
- **tracing**: For logging and diagnostics
|
||||
- **reqwest**: For HTTP client functionality
|
||||
738
backend/Cargo.lock
generated
738
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.496.3"
|
||||
version = "1.493.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -32,7 +32,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.496.3"
|
||||
version = "1.493.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -49,7 +49,6 @@ lto = "thin"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-api/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"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
|
||||
@@ -95,10 +94,8 @@ php = ["windmill-worker/php"]
|
||||
csharp = ["windmill-worker/csharp"]
|
||||
nu = ["windmill-worker/nu"]
|
||||
java = ["windmill-worker/java"]
|
||||
all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
# For windows we have another set of languages enabled
|
||||
# NOTE: DuckDB is ignored because of compilation problems
|
||||
all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
all_languages = [ "python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
|
||||
|
||||
[patch.crates-io]
|
||||
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# This script outputs all features except private. Usage :
|
||||
# > cargo build --features $(./all_features_oss.sh)
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Path to the Cargo.toml file
|
||||
CARGO_TOML_PATH="./Cargo.toml"
|
||||
|
||||
# Extract features from Cargo.toml and output them separated by commas
|
||||
if [[ -f "$CARGO_TOML_PATH" ]]; then
|
||||
grep -A 100 '\[features\]' "$CARGO_TOML_PATH" | \
|
||||
sed -n '/\[features\]/,/^\[/p' | \
|
||||
grep -E '^[a-zA-Z0-9_-]+' | \
|
||||
grep -v 'private' | \
|
||||
cut -d' ' -f1 | \
|
||||
paste -sd ',' -
|
||||
else
|
||||
echo "Cargo.toml not found at $CARGO_TOML_PATH"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1 +1 @@
|
||||
ab4f856708a056973faea19f10eda5efff546176
|
||||
32039f675060b5996951708368bdefe14278d5cd
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Remove token invalidation notification trigger
|
||||
|
||||
DROP TRIGGER IF EXISTS token_invalidation_trigger ON token;
|
||||
DROP FUNCTION IF EXISTS notify_token_invalidation();
|
||||
@@ -1,17 +0,0 @@
|
||||
-- Add token invalidation notification trigger
|
||||
|
||||
CREATE OR REPLACE FUNCTION notify_token_invalidation()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Only notify for session token deletions when the invalidation settings are enabled
|
||||
IF OLD.label = 'session' AND OLD.email IS NOT NULL THEN
|
||||
PERFORM pg_notify('notify_token_invalidation', OLD.token);
|
||||
END IF;
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER token_invalidation_trigger
|
||||
AFTER DELETE ON token
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_token_invalidation();
|
||||
@@ -1,13 +1,8 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn set_license_key(_license_key: String) -> () {
|
||||
// Implementation is not open source
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn verify_license_key() -> () {
|
||||
// Implementation is not open source
|
||||
}
|
||||
@@ -28,9 +28,7 @@ use uuid::Uuid;
|
||||
use windmill_api::HTTP_CLIENT;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::{
|
||||
maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID,
|
||||
};
|
||||
use windmill_common::ee::{maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID};
|
||||
|
||||
use windmill_common::{
|
||||
agent_workers::build_agent_http_client,
|
||||
@@ -51,12 +49,9 @@ use windmill_common::{
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
scripts::ScriptLang,
|
||||
stats_oss::schedule_stats,
|
||||
stats_ee::schedule_stats,
|
||||
triggers::TriggerKind,
|
||||
utils::{
|
||||
create_default_worker_suffix, create_ssh_agent_worker_suffix, worker_name_with_suffix,
|
||||
Mode, GIT_VERSION, HOSTNAME, MODE_AND_ADDONS,
|
||||
},
|
||||
utils::{hostname, rd_string, Mode, GIT_VERSION, MODE_AND_ADDONS},
|
||||
worker::{
|
||||
reload_custom_tags_setting, Connection, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP,
|
||||
},
|
||||
@@ -80,7 +75,8 @@ use windmill_worker::{
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR,
|
||||
DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR,
|
||||
JAVA_CACHE_DIR, NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR,
|
||||
PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, UV_CACHE_DIR,
|
||||
PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, TAR_PY310_CACHE_DIR,
|
||||
TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -102,9 +98,7 @@ const DEFAULT_NUM_WORKERS: usize = 1;
|
||||
const DEFAULT_PORT: u16 = 8000;
|
||||
const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
pub mod ee;
|
||||
mod ee_oss;
|
||||
mod ee;
|
||||
mod monitor;
|
||||
|
||||
pub fn setup_deno_runtime() -> anyhow::Result<()> {
|
||||
@@ -267,7 +261,7 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
tracing::error!("Failed to install rustls crypto provider");
|
||||
}
|
||||
|
||||
let hostname = HOSTNAME.to_owned();
|
||||
let hostname = hostname();
|
||||
|
||||
let mode_and_addons = MODE_AND_ADDONS.clone();
|
||||
let mode = mode_and_addons.mode;
|
||||
@@ -346,7 +340,7 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
"Creating http client for cluster using base internal url {}",
|
||||
std::env::var("BASE_INTERNAL_URL").unwrap_or_default()
|
||||
);
|
||||
let suffix = create_ssh_agent_worker_suffix(&hostname);
|
||||
let suffix = windmill_common::utils::worker_suffix(&hostname, &rd_string(5));
|
||||
(
|
||||
Connection::Http(build_agent_http_client(&suffix)),
|
||||
Some(suffix),
|
||||
@@ -558,7 +552,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
_ = indexer_rx.recv() => {
|
||||
tracing::info!("Received killpill, aborting index initialization");
|
||||
},
|
||||
res = windmill_indexer::completed_runs_oss::init_index(&db) => {
|
||||
res = windmill_indexer::completed_runs_ee::init_index(&db) => {
|
||||
let res = res?;
|
||||
reader = Some(res.0);
|
||||
writer = Some(res.1);
|
||||
@@ -580,7 +574,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
async {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
if let Some(index_writer) = index_writer2 {
|
||||
windmill_indexer::completed_runs_oss::run_indexer(
|
||||
windmill_indexer::completed_runs_ee::run_indexer(
|
||||
db.clone(),
|
||||
index_writer,
|
||||
indexer_rx,
|
||||
@@ -602,7 +596,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
_ = indexer_rx.recv() => {
|
||||
tracing::info!("Received killpill, aborting index initialization");
|
||||
},
|
||||
res = windmill_indexer::service_logs_oss::init_index(&db, killpill_tx.clone()) => {
|
||||
res = windmill_indexer::service_logs_ee::init_index(&db, killpill_tx.clone()) => {
|
||||
let res = res?;
|
||||
reader = Some(res.0);
|
||||
writer = Some(res.1);
|
||||
@@ -624,7 +618,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
async {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
if let Some(log_index_writer) = log_index_writer2 {
|
||||
windmill_indexer::service_logs_oss::run_indexer(
|
||||
windmill_indexer::service_logs_ee::run_indexer(
|
||||
db.clone(),
|
||||
log_index_writer,
|
||||
log_indexer_rx,
|
||||
@@ -681,21 +675,19 @@ Windmill Community Edition {GIT_VERSION}
|
||||
let base_internal_url = base_internal_rx.await?;
|
||||
if worker_mode {
|
||||
let mut workers = vec![];
|
||||
|
||||
for i in 0..num_workers {
|
||||
let suffix = if i == 0 && first_suffix.is_some() {
|
||||
let suffix: String = if i == 0 && first_suffix.as_ref().is_some() {
|
||||
first_suffix.as_ref().unwrap().clone()
|
||||
} else {
|
||||
create_default_worker_suffix(&hostname)
|
||||
windmill_common::utils::worker_suffix(&hostname, &rd_string(5))
|
||||
};
|
||||
|
||||
let worker_conn = WorkerConn {
|
||||
conn: if i == 0 || mode != Mode::Agent {
|
||||
conn.clone()
|
||||
} else {
|
||||
Connection::Http(build_agent_http_client(&suffix))
|
||||
},
|
||||
worker_name: worker_name_with_suffix(
|
||||
worker_name: windmill_common::utils::worker_name_with_suffix(
|
||||
mode == Mode::Agent,
|
||||
WORKER_GROUP.as_str(),
|
||||
&suffix,
|
||||
@@ -860,11 +852,6 @@ Windmill Community Edition {GIT_VERSION}
|
||||
}
|
||||
};
|
||||
},
|
||||
"notify_token_invalidation" => {
|
||||
let token = n.payload();
|
||||
tracing::info!("Token invalidation detected for token: {}...", &token[..token.len().min(8)]);
|
||||
windmill_api::auth::invalidate_token_from_cache(token);
|
||||
},
|
||||
"notify_global_setting_change" => {
|
||||
tracing::info!("Global setting change detected: {}", n.payload());
|
||||
match n.payload() {
|
||||
@@ -897,7 +884,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
if let Err(e) = load_tag_per_workspace_workspaces(&db).await {
|
||||
tracing::error!("Error loading default tag per workspace workspaces: {e:#}");
|
||||
}
|
||||
},
|
||||
}
|
||||
SMTP_SETTING => {
|
||||
reload_smtp_config(&db).await;
|
||||
},
|
||||
@@ -1014,6 +1001,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::error!(error = %e, "Could not reload critical alert UI setting");
|
||||
}
|
||||
},
|
||||
|
||||
a @_ => {
|
||||
tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a);
|
||||
}
|
||||
@@ -1098,7 +1086,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::info!("Reloading config after 12 hours");
|
||||
initial_load(&conn, tx.clone(), worker_mode, server_mode, #[cfg(feature = "parquet")] disable_s3_store).await;
|
||||
#[cfg(feature = "enterprise")]
|
||||
ee_oss::verify_license_key().await;
|
||||
ee::verify_license_key().await;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1186,7 +1174,6 @@ async fn listen_pg(url: &str) -> Option<PgListener> {
|
||||
"notify_webhook_change",
|
||||
"notify_workspace_envs_change",
|
||||
"notify_runnable_version_change",
|
||||
"notify_token_invalidation",
|
||||
];
|
||||
|
||||
#[cfg(feature = "http_trigger")]
|
||||
@@ -1280,6 +1267,10 @@ pub async fn run_workers(
|
||||
PY311_CACHE_DIR,
|
||||
PY312_CACHE_DIR,
|
||||
PY313_CACHE_DIR,
|
||||
TAR_PY310_CACHE_DIR,
|
||||
TAR_PY311_CACHE_DIR,
|
||||
TAR_PY312_CACHE_DIR,
|
||||
TAR_PY313_CACHE_DIR,
|
||||
BUN_BUNDLE_CACHE_DIR,
|
||||
GO_CACHE_DIR,
|
||||
GO_BIN_CACHE_DIR,
|
||||
|
||||
@@ -29,9 +29,9 @@ use windmill_api::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::low_disk_alerts;
|
||||
use windmill_common::ee::low_disk_alerts;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::{jobs_waiting_alerts, worker_groups_alerts};
|
||||
use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts};
|
||||
|
||||
use windmill_common::client::AuthedClient;
|
||||
#[cfg(feature = "oauth2")]
|
||||
@@ -41,7 +41,7 @@ use windmill_common::s3_helpers::reload_object_store_setting;
|
||||
use windmill_common::{
|
||||
agent_workers::DECODED_AGENT_TOKEN,
|
||||
auth::create_token_for_owner,
|
||||
ee_oss::CriticalErrorChannel,
|
||||
ee::CriticalErrorChannel,
|
||||
error,
|
||||
flow_status::{FlowStatus, FlowStatusModule},
|
||||
global_settings::{
|
||||
@@ -87,9 +87,9 @@ use windmill_worker::{
|
||||
use windmill_common::s3_helpers::ObjectStoreReload;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee_oss::verify_license_key;
|
||||
use crate::ee::verify_license_key;
|
||||
|
||||
use crate::ee_oss::set_license_key;
|
||||
use crate::ee::set_license_key;
|
||||
|
||||
#[cfg(feature = "prometheus")]
|
||||
lazy_static::lazy_static! {
|
||||
@@ -1609,7 +1609,7 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
|
||||
|
||||
if let Some(q) = q_oauth {
|
||||
if let Ok(v) = serde_json::from_value::<
|
||||
Option<HashMap<String, windmill_api::oauth2_oss::OAuthClient>>,
|
||||
Option<HashMap<String, windmill_api::oauth2_ee::OAuthClient>>,
|
||||
>(q.clone())
|
||||
{
|
||||
v
|
||||
@@ -1630,7 +1630,7 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
|
||||
{
|
||||
if let Some(db) = conn.as_sql() {
|
||||
let mut l = windmill_api::OAUTH_CLIENTS.write().await;
|
||||
*l = windmill_api::oauth2_oss::build_oauth_clients(&base_url, oauths, db).await
|
||||
*l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths, db).await
|
||||
.map_err(|e| tracing::error!("Error building oauth clients (is the oauth.json mounted and in correct format? Use '{}' as minimal oauth.json): {}", "{}", e))
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1914,12 +1914,12 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
|
||||
let client = AuthedClient::new(
|
||||
base_internal_url.to_string(),
|
||||
job.workspace_id.to_string(),
|
||||
let client = AuthedClient {
|
||||
base_internal_url: base_internal_url.to_string(),
|
||||
token,
|
||||
None,
|
||||
);
|
||||
workspace: job.workspace_id.to_string(),
|
||||
force_client: None,
|
||||
};
|
||||
|
||||
let last_ping = job.last_ping.clone();
|
||||
let error_message = format!(
|
||||
@@ -1938,7 +1938,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
None,
|
||||
error::Error::ExecutionErr(error_message),
|
||||
true,
|
||||
Some(&same_worker_tx_never_used),
|
||||
same_worker_tx_never_used,
|
||||
"",
|
||||
worker_name,
|
||||
send_result_never_used,
|
||||
|
||||
@@ -4,8 +4,8 @@ script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"
|
||||
|
||||
REVERT="NO"
|
||||
REVERT_PREVIOUS="NO"
|
||||
COPY="NO"
|
||||
MOVE_NEW_FILES="NO"
|
||||
EE_CODE_DIR="../windmill-ee-private/"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -16,7 +16,13 @@ while [[ $# -gt 0 ]]; do
|
||||
# this to work (commit hooks should prevent this from happening, as well as the fact
|
||||
# that we're using symlinks by default).
|
||||
REVERT="YES"
|
||||
MOVE_NEW_FILES="YES"
|
||||
shift
|
||||
;;
|
||||
--revert-previous)
|
||||
# This is a special case of --revert that will revert to the previous commit.
|
||||
REVERT="YES"
|
||||
REVERT_PREVIOUS="YES"
|
||||
echo "Reverting to previous commit"
|
||||
shift
|
||||
;;
|
||||
-c|--copy)
|
||||
@@ -27,11 +33,6 @@ while [[ $# -gt 0 ]]; do
|
||||
COPY="YES"
|
||||
shift # past argument
|
||||
;;
|
||||
-m|--move-new-files)
|
||||
# This moves all new EE files from the public repository to the private repository.
|
||||
MOVE_NEW_FILES="YES"
|
||||
shift # past argument
|
||||
;;
|
||||
-d|--dir)
|
||||
# Path to the local directory of the windmill-ee-private repository. By defaults, it
|
||||
# assumes it is cloned next to the Windmill OSS repo.
|
||||
@@ -69,34 +70,29 @@ if [ "$REVERT" == "YES" ]; then
|
||||
for ee_file in $(find ${EE_CODE_DIR} -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
rm ${ce_file} || true
|
||||
if [ "$REVERT_PREVIOUS" == "YES" ]; then
|
||||
git checkout HEAD@{3} ${ce_file} || true
|
||||
else
|
||||
git restore --staged ${ce_file} || true
|
||||
git restore ${ce_file} || true
|
||||
fi
|
||||
done
|
||||
elif [ "$MOVE_NEW_FILES" == "NO" ]; then
|
||||
else
|
||||
# This replaces all files in current repo with alternative EE files in windmill-ee-private
|
||||
for ee_file in $(find "${EE_CODE_DIR}" -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
if [ "$COPY" == "YES" ]; then
|
||||
cp "${ee_file}" "${ce_file}"
|
||||
echo "File copied '${ee_file}' -->> '${ce_file}'"
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
if [[ -f "${ce_file}" ]]; then
|
||||
rm "${ce_file}"
|
||||
if [ "$COPY" == "YES" ]; then
|
||||
cp "${ee_file}" "${ce_file}"
|
||||
echo "File copied '${ee_file}' -->> '${ce_file}'"
|
||||
else
|
||||
ln -s "${ee_file}" "${ce_file}"
|
||||
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
|
||||
fi
|
||||
else
|
||||
ln -s "${ee_file}" "${ce_file}" || true
|
||||
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
|
||||
echo "File ${ce_file} is not a file, ignoring"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$MOVE_NEW_FILES" == "YES" ]; then
|
||||
for ce_file in $(find "${root_dirpath}"/backend/windmill-*/src/ -name "*ee.rs"); do
|
||||
backend_dirpath="${root_dirpath}/backend/"
|
||||
ee_file="${ce_file/${backend_dirpath}/}"
|
||||
ee_file="${EE_CODE_DIR}${ee_file}"
|
||||
if [ ! -f "${ee_file}" ]; then
|
||||
mv "${ce_file}" "${ee_file}"
|
||||
if [ ! "$REVERT" == "YES" ]; then
|
||||
ln -s "${ee_file}" "${ce_file}"
|
||||
fi
|
||||
echo "File moved '${ce_file}' -->> '${ee_file}'"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@@ -3969,7 +3969,8 @@ async fn assert_lockfile(
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_requirements_python(db: Pool<Postgres>) {
|
||||
let content = r#"# py: ==3.11.11
|
||||
let content = r#"
|
||||
# py: 3.11.11
|
||||
# requirements:
|
||||
# tiny==0.1.3
|
||||
|
||||
@@ -3996,7 +3997,8 @@ def main():
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_extra_requirements_python(db: Pool<Postgres>) {
|
||||
{
|
||||
let content = r#"# py: ==3.11.11
|
||||
let content = r#"
|
||||
# py: ==3.11.11
|
||||
# extra_requirements:
|
||||
# tiny
|
||||
|
||||
@@ -4023,7 +4025,8 @@ def main():
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_extra_requirements_python2(db: Pool<Postgres>) {
|
||||
let content = r#"# py: ==3.11.11
|
||||
let content = r#"
|
||||
# py: ==3.11.11
|
||||
# extra_requirements:
|
||||
# tiny==0.1.3
|
||||
|
||||
@@ -4045,7 +4048,8 @@ def main():
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_pins_python(db: Pool<Postgres>) {
|
||||
let content = r#"# py: ==3.11.11
|
||||
let content = r#"
|
||||
# py: ==3.11.11
|
||||
# extra_requirements:
|
||||
# tiny==0.1.3
|
||||
# bottle==0.13.2
|
||||
|
||||
@@ -10,6 +10,7 @@ if [[ "$(uname)" == "Darwin" ]]; then
|
||||
fi
|
||||
|
||||
cargo sqlx prepare --workspace -- --all-targets --all-features
|
||||
./substitute_ee_code.sh -r --dir ../windmill-ee-private
|
||||
|
||||
# Undo the samael changes on macOS
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
|
||||
@@ -10,7 +10,6 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-audit/private"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker/enterprise"]
|
||||
stripe = []
|
||||
agent_worker_server = []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.496.3
|
||||
version: 1.493.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -377,9 +377,6 @@ paths:
|
||||
type: string
|
||||
company:
|
||||
type: string
|
||||
skip_email:
|
||||
type: boolean
|
||||
description: Skip sending email notifications to the user
|
||||
required:
|
||||
- email
|
||||
- password
|
||||
@@ -4501,13 +4498,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: languages
|
||||
in: query
|
||||
description: |
|
||||
Filter to only include scripts written in the given languages.
|
||||
Accepts multiple values as a comma-separated list.
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: All scripts
|
||||
@@ -4903,6 +4893,8 @@ paths:
|
||||
description: Script version/hash
|
||||
content:
|
||||
application/json:
|
||||
required: false
|
||||
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptHistory"
|
||||
|
||||
@@ -5464,6 +5456,8 @@ paths:
|
||||
description: Flow version
|
||||
content:
|
||||
application/json:
|
||||
required: false
|
||||
|
||||
schema:
|
||||
$ref: "#/components/schemas/FlowVersion"
|
||||
|
||||
@@ -5493,7 +5487,8 @@ paths:
|
||||
operationId: getFlowVersion
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
- type: string
|
||||
name: version
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
@@ -5515,7 +5510,8 @@ paths:
|
||||
operationId: updateFlowHistory
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
- type: string
|
||||
name: version
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
@@ -6143,8 +6139,10 @@ paths:
|
||||
description: App version
|
||||
content:
|
||||
application/json:
|
||||
required: false
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppHistory"
|
||||
|
||||
/w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}:
|
||||
get:
|
||||
summary: list app paths from workspace runnable
|
||||
@@ -6733,6 +6731,11 @@ paths:
|
||||
responses:
|
||||
"201":
|
||||
description: stream of created job uuids separated by \n. Lines may start with 'Error:'
|
||||
example: |
|
||||
a1a74c0d-708e-4539-9768-e8b3d37996bd
|
||||
f0949132-5b30-48fe-bac8-873f047df810
|
||||
Error: Could not re-run 0b885808-ae89-4458-af95-c1ca3a13b0a5
|
||||
52b9c01d-1125-4bbb-8bee-d41f26b70066
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
@@ -7589,8 +7592,7 @@ paths:
|
||||
description: job log
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs_u/get_flow_debug_info/{id}:
|
||||
get:
|
||||
@@ -8451,31 +8453,6 @@ paths:
|
||||
"201":
|
||||
description: default error handler set
|
||||
|
||||
/w/{workspace}/http_triggers/create_many:
|
||||
post:
|
||||
summary: create many HTTP triggers
|
||||
operationId: createHttpTriggers
|
||||
tags:
|
||||
- http_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: new http trigger
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NewHttpTrigger"
|
||||
responses:
|
||||
"201":
|
||||
description: http trigger created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/http_triggers/create:
|
||||
post:
|
||||
summary: create http trigger
|
||||
@@ -8627,7 +8604,8 @@ paths:
|
||||
route_path:
|
||||
type: string
|
||||
http_method:
|
||||
$ref: "#/components/schemas/HttpMethod"
|
||||
type: string
|
||||
enum: ["get", "post", "put", "delete", "patch"]
|
||||
trigger_path:
|
||||
type: string
|
||||
workspaced_route:
|
||||
@@ -9852,23 +9830,6 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/postgres_triggers/postgres/version/{path}:
|
||||
get:
|
||||
summary: get postgres version
|
||||
operationId: getPostgresVersion
|
||||
tags:
|
||||
- postgres_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: postgres version
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}:
|
||||
get:
|
||||
summary: check if postgres configuration is set to logical
|
||||
@@ -12508,6 +12469,7 @@ paths:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/concurrency_groups/list:
|
||||
get:
|
||||
summary: List all concurrency groups
|
||||
@@ -13213,51 +13175,7 @@ components:
|
||||
enum: [script, flow]
|
||||
|
||||
schemas:
|
||||
# NOTE: Not so many generators and validators support this format:
|
||||
# $ref: "../../openflow.openapi.yaml#/components/schemas"
|
||||
# This is why it is better to inline each of schemas for better compat
|
||||
# Do not change next line. It is used by python-client for pre-processing
|
||||
# -- INLINE START --
|
||||
OpenFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow"
|
||||
FlowValue:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowValue"
|
||||
Retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
StopAfterIf:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/StopAfterIf"
|
||||
FlowModule:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowModule"
|
||||
InputTransform:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/InputTransform"
|
||||
StaticTransform:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/StaticTransform"
|
||||
JavascriptTransform:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/JavascriptTransform"
|
||||
FlowModuleValue:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowModuleValue"
|
||||
RawScript:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/RawScript"
|
||||
PathScript:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/PathScript"
|
||||
PathFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/PathFlow"
|
||||
ForloopFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/ForloopFlow"
|
||||
WhileloopFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/WhileloopFlow"
|
||||
BranchOne:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchOne"
|
||||
BranchAll:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchAll"
|
||||
Identity:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Identity"
|
||||
FlowStatus:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatus"
|
||||
FlowStatusModule:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatusModule"
|
||||
# -- INLINE END --
|
||||
# Do not change line above
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas"
|
||||
|
||||
AIProvider:
|
||||
type: string
|
||||
@@ -14754,15 +14672,6 @@ components:
|
||||
- custom_script
|
||||
- signature
|
||||
|
||||
HttpMethod:
|
||||
type: string
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
- put
|
||||
- delete
|
||||
- patch
|
||||
|
||||
HttpTrigger:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/TriggerExtraProperty"
|
||||
@@ -14782,7 +14691,13 @@ components:
|
||||
required:
|
||||
- s3
|
||||
http_method:
|
||||
$ref: "#/components/schemas/HttpMethod"
|
||||
type: string
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
- put
|
||||
- delete
|
||||
- patch
|
||||
authentication_resource_path:
|
||||
type: string
|
||||
is_async:
|
||||
@@ -14833,7 +14748,13 @@ components:
|
||||
is_flow:
|
||||
type: boolean
|
||||
http_method:
|
||||
$ref: "#/components/schemas/HttpMethod"
|
||||
type: string
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
- put
|
||||
- delete
|
||||
- patch
|
||||
authentication_resource_path:
|
||||
type: string
|
||||
is_async:
|
||||
@@ -14884,7 +14805,13 @@ components:
|
||||
is_flow:
|
||||
type: boolean
|
||||
http_method:
|
||||
$ref: "#/components/schemas/HttpMethod"
|
||||
type: string
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
- put
|
||||
- delete
|
||||
- patch
|
||||
is_async:
|
||||
type: boolean
|
||||
authentication_method:
|
||||
@@ -16298,8 +16225,10 @@ components:
|
||||
- access_token
|
||||
|
||||
HubScriptKind:
|
||||
type: string
|
||||
enum: [script, failure, trigger, approval]
|
||||
name: kind
|
||||
schema:
|
||||
type: string
|
||||
enum: [script, failure, trigger, approval]
|
||||
|
||||
PolarsClientKwargs:
|
||||
type: object
|
||||
|
||||
52
backend/windmill-api/src/agent_workers_ee.rs
Normal file
52
backend/windmill-api/src/agent_workers_ee.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
* 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 crate::db::DB;
|
||||
|
||||
use axum::Router;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn workspaced_service(
|
||||
db: DB,
|
||||
_base_internal_url: String,
|
||||
) -> (
|
||||
Router,
|
||||
Vec<tokio::task::JoinHandle<()>>,
|
||||
Option<windmill_worker::JobCompletedSender>,
|
||||
) {
|
||||
use windmill_common::worker::Connection;
|
||||
use windmill_worker::JobCompletedSender;
|
||||
|
||||
let (job_completed_tx, _job_completed_rx) =
|
||||
JobCompletedSender::new(&Connection::Sql(db.clone()), 10);
|
||||
|
||||
let router = Router::new();
|
||||
|
||||
(router, vec![], Some(job_completed_tx))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct AgentAuth {
|
||||
pub worker_group: String,
|
||||
pub suffix: Option<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub exp: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct AgentCache {}
|
||||
|
||||
impl AgentCache {
|
||||
pub fn new() -> Self {
|
||||
AgentCache {}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::agent_workers_ee::*;
|
||||
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
@@ -10,21 +6,16 @@ pub use crate::agent_workers_ee::*;
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
crate::agent_workers_ee::global_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service(
|
||||
db: DB,
|
||||
_base_internal_url: String,
|
||||
@@ -33,32 +24,8 @@ pub fn workspaced_service(
|
||||
Vec<tokio::task::JoinHandle<()>>,
|
||||
Option<windmill_worker::JobCompletedSender>,
|
||||
) {
|
||||
use windmill_common::worker::Connection;
|
||||
use windmill_worker::JobCompletedSender;
|
||||
|
||||
let (job_completed_tx, _job_completed_rx) =
|
||||
JobCompletedSender::new(&Connection::Sql(db.clone()), 10);
|
||||
|
||||
let router = Router::new();
|
||||
|
||||
(router, vec![], Some(job_completed_tx))
|
||||
crate::agent_workers_ee::workspaced_service(db, _base_internal_url)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct AgentAuth {
|
||||
pub worker_group: String,
|
||||
pub suffix: Option<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub exp: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct AgentCache {}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl AgentCache {
|
||||
pub fn new() -> Self {
|
||||
AgentCache {}
|
||||
}
|
||||
}
|
||||
pub use crate::agent_workers_ee::AgentAuth;
|
||||
pub use crate::agent_workers_ee::AgentCache;
|
||||
|
||||
@@ -10,7 +10,7 @@ use reqwest::{Client, RequestBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use std::collections::HashMap;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::error::{to_anyhow, Error, Result};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::{
|
||||
job_helpers_oss::{
|
||||
job_helpers_ee::{
|
||||
download_s3_file_internal, get_random_file_name, get_s3_resource,
|
||||
get_workspace_s3_resource, upload_file_from_req, DownloadFileQuery,
|
||||
},
|
||||
@@ -48,7 +48,7 @@ use sha2::{Digest, Sha256};
|
||||
use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::{types::Uuid, FromRow};
|
||||
use std::str;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
apps::{AppScriptId, ListAppQuery},
|
||||
|
||||
5
backend/windmill-api/src/apps_ee.rs
Normal file
5
backend/windmill-api/src/apps_ee.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::apps_ee::*;
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
crate::apps_ee::global_unauthed_service()
|
||||
}
|
||||
@@ -85,7 +85,7 @@ impl RawWebhookArgs {
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> Result<HashMap<String, Box<RawValue>>, Error> {
|
||||
use crate::job_helpers_oss::{
|
||||
use crate::job_helpers_ee::{
|
||||
get_random_file_name, get_workspace_s3_resource, upload_file_internal,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
|
||||
@@ -28,7 +28,7 @@ async fn get_audit(
|
||||
Path((w_id, id)): Path<(String, i32)>,
|
||||
) -> JsonResult<AuditLog> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let audit = windmill_audit::audit_oss::get_audit(tx, id, &w_id).await?;
|
||||
let audit = windmill_audit::audit_ee::get_audit(tx, id, &w_id).await?;
|
||||
Ok(Json(audit))
|
||||
}
|
||||
async fn list_audit(
|
||||
@@ -39,6 +39,6 @@ async fn list_audit(
|
||||
Query(lq): Query<ListAuditLogQuery>,
|
||||
) -> JsonResult<Vec<AuditLog>> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let rows = windmill_audit::audit_oss::list_audit(tx, w_id, pagination, lq).await?;
|
||||
let rows = windmill_audit::audit_ee::list_audit(tx, w_id, pagination, lq).await?;
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee_oss::ExternalJwks;
|
||||
use crate::ee::ExternalJwks;
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequestParts, OriginalUri, Query},
|
||||
@@ -26,20 +26,6 @@ use windmill_common::{
|
||||
users::{COOKIE_NAME, SUPERADMIN_SECRET_EMAIL},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
// Global auth cache accessible from main.rs for direct invalidation
|
||||
pub static ref AUTH_CACHE: Cache<(String, String), ExpiringAuthCache> = Cache::new(300);
|
||||
}
|
||||
|
||||
// Global function to invalidate a specific token from cache
|
||||
pub fn invalidate_token_from_cache(token: &str) {
|
||||
// Remove all cache entries for this token (across all workspaces)
|
||||
AUTH_CACHE.retain(|(_workspace_id, cached_token), _cached_value| {
|
||||
cached_token != token
|
||||
});
|
||||
tracing::info!("Invalidated token from auth cache: {}...", &token[..token.len().min(8)]);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExpiringAuthCache {
|
||||
pub authed: ApiAuthed,
|
||||
@@ -47,6 +33,7 @@ pub struct ExpiringAuthCache {
|
||||
}
|
||||
|
||||
pub struct AuthCache {
|
||||
cache: Cache<(String, String), ExpiringAuthCache>,
|
||||
db: DB,
|
||||
superadmin_secret: Option<String>,
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -60,6 +47,7 @@ impl AuthCache {
|
||||
#[cfg(feature = "enterprise")] ext_jwks: Option<Arc<RwLock<ExternalJwks>>>,
|
||||
) -> Self {
|
||||
AuthCache {
|
||||
cache: Cache::new(300),
|
||||
db,
|
||||
superadmin_secret,
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -68,7 +56,7 @@ impl AuthCache {
|
||||
}
|
||||
|
||||
pub async fn invalidate(&self, w_id: &str, token: String) {
|
||||
AUTH_CACHE.remove(&(w_id.to_string(), token));
|
||||
self.cache.remove(&(w_id.to_string(), token));
|
||||
}
|
||||
|
||||
pub async fn get_authed(&self, w_id: Option<String>, token: &str) -> Option<ApiAuthed> {
|
||||
@@ -76,14 +64,14 @@ impl AuthCache {
|
||||
w_id.as_ref().unwrap_or(&"".to_string()).to_string(),
|
||||
token.to_string(),
|
||||
);
|
||||
let s = AUTH_CACHE.get(&key).map(|c| c.to_owned());
|
||||
let s = self.cache.get(&key).map(|c| c.to_owned());
|
||||
match s {
|
||||
Some(ExpiringAuthCache { authed, expiry }) if expiry > chrono::Utc::now() => {
|
||||
Some(authed)
|
||||
}
|
||||
#[cfg(feature = "enterprise")]
|
||||
_ if token.starts_with("jwt_ext_") => {
|
||||
let authed_and_exp = match crate::ee_oss::jwt_ext_auth(
|
||||
let authed_and_exp = match crate::ee::jwt_ext_auth(
|
||||
w_id.as_ref(),
|
||||
token.trim_start_matches("jwt_ext_"),
|
||||
self.ext_jwks.clone(),
|
||||
@@ -98,7 +86,7 @@ impl AuthCache {
|
||||
};
|
||||
|
||||
if let Some((authed, exp)) = authed_and_exp.clone() {
|
||||
AUTH_CACHE.insert(
|
||||
self.cache.insert(
|
||||
key,
|
||||
ExpiringAuthCache {
|
||||
authed: authed.clone(),
|
||||
@@ -135,7 +123,7 @@ impl AuthCache {
|
||||
username_override,
|
||||
};
|
||||
|
||||
AUTH_CACHE.insert(
|
||||
self.cache.insert(
|
||||
key,
|
||||
ExpiringAuthCache {
|
||||
authed: authed.clone(),
|
||||
@@ -329,7 +317,7 @@ impl AuthCache {
|
||||
}
|
||||
};
|
||||
if let Some(authed) = authed_o.as_ref() {
|
||||
AUTH_CACHE.insert(
|
||||
self.cache.insert(
|
||||
key,
|
||||
ExpiringAuthCache {
|
||||
authed: authed.clone(),
|
||||
|
||||
@@ -15,7 +15,7 @@ use {
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
use {
|
||||
crate::gcp_triggers_oss::{
|
||||
crate::gcp_triggers_ee::{
|
||||
manage_google_subscription, process_google_push_request, validate_jwt_token,
|
||||
CreateUpdateConfig, SubscriptionMode,
|
||||
},
|
||||
@@ -23,10 +23,7 @@ use {
|
||||
http::HeaderMap,
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "enterprise", feature = "gcp_trigger"),
|
||||
feature = "postgres_trigger"
|
||||
))]
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
use windmill_common::utils::empty_as_none;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
|
||||
@@ -36,28 +33,25 @@ use windmill_common::auth::aws::AwsAuthResourceType;
|
||||
feature = "http_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
))]
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "http_trigger",
|
||||
feature = "postgres_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
))]
|
||||
use windmill_common::error::Error;
|
||||
use {serde::de::DeserializeOwned, windmill_common::error::Error};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
use crate::kafka_triggers_oss::KafkaTriggerConfigConnection;
|
||||
use crate::kafka_triggers_ee::KafkaTriggerConfigConnection;
|
||||
|
||||
#[cfg(feature = "mqtt_trigger")]
|
||||
use crate::mqtt_triggers::{MqttClientVersion, MqttV3Config, MqttV5Config, SubscribeTopic};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
use crate::nats_triggers_oss::NatsTriggerConfigConnection;
|
||||
use crate::nats_triggers_ee::NatsTriggerConfigConnection;
|
||||
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
use crate::postgres_triggers::{
|
||||
create_logical_replication_slot, create_pg_publication, generate_random_string,
|
||||
get_default_pg_connection, PublicationData,
|
||||
use {
|
||||
crate::postgres_triggers::{
|
||||
create_logical_replication_slot_query, create_publication_query, drop_publication_query,
|
||||
generate_random_string, get_database_connection, PublicationData,
|
||||
},
|
||||
itertools::Itertools,
|
||||
pg_escape::quote_literal,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -206,12 +200,9 @@ pub struct MqttTriggerConfig {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PostgresTriggerConfig {
|
||||
pub postgres_resource_path: String,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub publication_name: Option<String>,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub replication_slot_name: Option<String>,
|
||||
pub publication: PublicationData,
|
||||
pub basic_mode: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
@@ -301,55 +292,57 @@ async fn set_postgres_trigger_config(
|
||||
user_db: UserDB,
|
||||
mut capture_config: NewCaptureConfig,
|
||||
) -> Result<NewCaptureConfig> {
|
||||
use windmill_common::error::to_anyhow;
|
||||
|
||||
let Some(TriggerConfig::Postgres(postgres_config)) = capture_config.trigger_config.as_mut()
|
||||
else {
|
||||
return Err(Error::BadRequest("Invalid postgres config".to_string()));
|
||||
let Some(TriggerConfig::Postgres(mut postgres_config)) = capture_config.trigger_config else {
|
||||
return Err(windmill_common::error::Error::BadRequest(
|
||||
"Invalid postgres config".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
if postgres_config.basic_mode.unwrap_or(false) {
|
||||
let mut pg_connection = get_default_pg_connection(
|
||||
authed,
|
||||
Some(user_db),
|
||||
&db,
|
||||
&postgres_config.postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
let mut connection = get_database_connection(
|
||||
authed,
|
||||
Some(user_db),
|
||||
&db,
|
||||
&postgres_config.postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tx = pg_connection.transaction().await.map_err(to_anyhow)?;
|
||||
let publication_name = postgres_config
|
||||
.publication_name
|
||||
.get_or_insert(format!("windmill_capture_{}", generate_random_string()));
|
||||
let replication_slot_name = postgres_config
|
||||
.replication_slot_name
|
||||
.get_or_insert(publication_name.clone());
|
||||
|
||||
let publication_name = format!("windmill_capture_{}", generate_random_string());
|
||||
let replication_slot_name = publication_name.clone();
|
||||
let query = drop_publication_query(&publication_name);
|
||||
|
||||
create_logical_replication_slot(tx.client(), &replication_slot_name)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
create_pg_publication(
|
||||
tx.client(),
|
||||
&publication_name,
|
||||
postgres_config.publication.table_to_track.as_deref(),
|
||||
&postgres_config.publication.transaction_to_track,
|
||||
)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
let query = create_publication_query(
|
||||
&publication_name,
|
||||
postgres_config.publication.table_to_track.as_deref(),
|
||||
&postgres_config
|
||||
.publication
|
||||
.transaction_to_track
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.collect_vec(),
|
||||
);
|
||||
|
||||
tx.commit().await.map_err(to_anyhow)?;
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
postgres_config.publication_name = Some(publication_name);
|
||||
postgres_config.replication_slot_name = Some(replication_slot_name);
|
||||
} else {
|
||||
if postgres_config.publication_name.is_none()
|
||||
|| postgres_config.replication_slot_name.is_none()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
"Publication name and slot name required in advanced mode".to_string(),
|
||||
));
|
||||
}
|
||||
let query = format!(
|
||||
"SELECT 1 from pg_replication_slots WHERE slot_name = {}",
|
||||
quote_literal(replication_slot_name)
|
||||
);
|
||||
|
||||
let row = sqlx::query(&query).fetch_optional(&mut connection).await?;
|
||||
|
||||
if row.is_none() {
|
||||
let query = create_logical_replication_slot_query(&replication_slot_name);
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
}
|
||||
|
||||
capture_config.trigger_config = Some(TriggerConfig::Postgres(postgres_config));
|
||||
Ok(capture_config)
|
||||
}
|
||||
|
||||
@@ -373,7 +366,9 @@ async fn set_gcp_trigger_config(
|
||||
mut capture_config: NewCaptureConfig,
|
||||
) -> Result<NewCaptureConfig> {
|
||||
let Some(TriggerConfig::Gcp(mut gcp_config)) = capture_config.trigger_config else {
|
||||
return Err(Error::BadRequest("Invalid GCP Pub/Sub config".to_string()));
|
||||
return Err(windmill_common::error::Error::BadRequest(
|
||||
"Invalid GCP Pub/Sub config".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let config = manage_google_subscription(
|
||||
@@ -908,7 +903,7 @@ async fn gcp_payload(
|
||||
headers: HeaderMap,
|
||||
request: Request,
|
||||
) -> Result<StatusCode> {
|
||||
use crate::{gcp_triggers_oss::GcpTrigger, trigger_helpers::TriggerJobArgs};
|
||||
use crate::{gcp_triggers_ee::GcpTrigger, trigger_helpers::TriggerJobArgs};
|
||||
|
||||
let is_flow = matches!(runnable_kind, RunnableKind::Flow);
|
||||
let (gcp_trigger_config, owner, email): (GcpTriggerConfig, _, _) =
|
||||
|
||||
@@ -14,7 +14,7 @@ use axum::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
error::{self},
|
||||
|
||||
@@ -16,7 +16,7 @@ use sqlx::{
|
||||
};
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use windmill_audit::audit_oss::{AuditAuthor, AuditAuthorable};
|
||||
use windmill_audit::audit_ee::{AuditAuthor, AuditAuthorable};
|
||||
use windmill_common::{
|
||||
db::{Authable, Authed},
|
||||
error::Error,
|
||||
@@ -804,14 +804,6 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
|
||||
.execute(db)
|
||||
.await?;
|
||||
});
|
||||
|
||||
run_windmill_migration!("v2_job_queue_suspend", db, |tx| {
|
||||
sqlx::query!(
|
||||
"CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;"
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use anyhow::anyhow;
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<(String, bool)> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License can't be validated in Windmill CE"))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn jwt_ext_auth(
|
||||
_w_id: Option<&String>,
|
||||
_token: &str,
|
||||
@@ -26,10 +20,10 @@ pub async fn jwt_ext_auth(
|
||||
Err(anyhow!("External JWT auth is not open source"))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub struct ExternalJwks;
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
impl ExternalJwks {
|
||||
pub async fn load() -> Option<Arc<RwLock<Self>>> {
|
||||
// Implementation is not open source
|
||||
@@ -31,7 +31,7 @@ use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_builder::prelude::*;
|
||||
use sqlx::{FromRow, Postgres, Transaction};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::utils::query_elems_from_hub;
|
||||
use windmill_common::worker::to_raw_value;
|
||||
|
||||
@@ -23,7 +23,7 @@ use axum::{
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
|
||||
147
backend/windmill-api/src/gcp_triggers_ee.rs
Normal file
147
backend/windmill-api/src/gcp_triggers_ee.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
use crate::trigger_helpers::TriggerJobArgs;
|
||||
use axum::{extract::Request, Router};
|
||||
use http::HeaderMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::prelude::FromRow;
|
||||
use sqlx::types::Json as SqlxJson;
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::worker::to_raw_value;
|
||||
use windmill_common::{
|
||||
error::{Error as WindmillError, Result as WindmillResult},
|
||||
triggers::TriggerKind,
|
||||
utils::empty_as_none,
|
||||
};
|
||||
|
||||
#[derive(sqlx::Type, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
#[sqlx(type_name = "DELIVERY_MODE", rename_all = "lowercase")]
|
||||
#[allow(unused)]
|
||||
pub enum DeliveryType {
|
||||
Pull,
|
||||
Push,
|
||||
}
|
||||
|
||||
impl Default for DeliveryType {
|
||||
fn default() -> Self {
|
||||
Self::Pull
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize, Debug)]
|
||||
#[allow(unused)]
|
||||
pub struct PushConfig {
|
||||
#[serde(deserialize_with = "empty_as_none")]
|
||||
route_path: Option<String>,
|
||||
#[serde(deserialize_with = "empty_as_none")]
|
||||
audience: Option<String>,
|
||||
authenticate: bool,
|
||||
base_endpoint: String,
|
||||
}
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct CreateUpdateConfig {
|
||||
pub delivery_type: DeliveryType,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub subscription_id: Option<String>,
|
||||
pub delivery_config: Option<SqlxJson<PushConfig>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ExistingGcpSubscription {
|
||||
pub subscription_id: String,
|
||||
pub base_endpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, sqlx::Type)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[sqlx(type_name = "GCP_SUBSCRIPTION_MODE", rename_all = "snake_case")]
|
||||
pub enum SubscriptionMode {
|
||||
Existing,
|
||||
CreateUpdate,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn start_consuming_gcp_pubsub_event(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
pub async fn manage_google_subscription(
|
||||
_authed: ApiAuthed,
|
||||
_db: &DB,
|
||||
_workspace_id: &str,
|
||||
_gcp_resource_path: &str,
|
||||
_path: &str,
|
||||
_topic_id: &str,
|
||||
_subscription_id: &mut Option<String>,
|
||||
_base_endpoint: &mut Option<String>,
|
||||
_subscription_mode: SubscriptionMode,
|
||||
_create_update_config: Option<CreateUpdateConfig>,
|
||||
_trigger_mode: bool,
|
||||
_is_flow: bool,
|
||||
) -> WindmillResult<CreateUpdateConfig> {
|
||||
Ok(CreateUpdateConfig::default())
|
||||
}
|
||||
|
||||
pub async fn process_google_push_request(
|
||||
_headers: HeaderMap,
|
||||
_request: Request,
|
||||
) -> Result<(String, HashMap<String, Box<RawValue>>), WindmillError> {
|
||||
Ok((String::new(), HashMap::new()))
|
||||
}
|
||||
|
||||
pub async fn validate_jwt_token(
|
||||
_db: &DB,
|
||||
_user_db: UserDB,
|
||||
_authed: ApiAuthed,
|
||||
_headers: &HeaderMap,
|
||||
_gcp_resource_path: &str,
|
||||
_workspace_id: &str,
|
||||
_delivery_config: &PushConfig,
|
||||
) -> Result<(), windmill_common::error::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gcp_push_route_handler() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize, Debug)]
|
||||
pub struct GcpTrigger {
|
||||
pub gcp_resource_path: String,
|
||||
pub subscription_id: String,
|
||||
pub delivery_type: DeliveryType,
|
||||
pub delivery_config: Option<SqlxJson<PushConfig>>,
|
||||
pub subscription_mode: SubscriptionMode,
|
||||
pub topic_id: String,
|
||||
pub path: String,
|
||||
pub script_path: String,
|
||||
pub is_flow: bool,
|
||||
pub workspace_id: String,
|
||||
pub edited_by: String,
|
||||
pub email: String,
|
||||
pub edited_at: chrono::DateTime<chrono::Utc>,
|
||||
pub extra_perms: Option<serde_json::Value>,
|
||||
pub error: Option<String>,
|
||||
pub server_id: Option<String>,
|
||||
pub last_server_ping: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl TriggerJobArgs<String> for GcpTrigger {
|
||||
fn v1_payload_fn(payload: String) -> HashMap<String, Box<RawValue>> {
|
||||
HashMap::from([("payload".to_string(), to_raw_value(&payload))])
|
||||
}
|
||||
|
||||
fn trigger_kind() -> TriggerKind {
|
||||
TriggerKind::Gcp
|
||||
}
|
||||
}
|
||||
@@ -1,167 +1,61 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::gcp_triggers_ee::*;
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use {
|
||||
crate::db::{ApiAuthed, DB},
|
||||
crate::trigger_helpers::TriggerJobArgs,
|
||||
axum::{extract::Request, Router},
|
||||
http::HeaderMap,
|
||||
serde::{Deserialize, Serialize},
|
||||
serde_json::value::RawValue,
|
||||
sqlx::prelude::FromRow,
|
||||
sqlx::types::Json as SqlxJson,
|
||||
std::collections::HashMap,
|
||||
windmill_common::db::UserDB,
|
||||
windmill_common::worker::to_raw_value,
|
||||
windmill_common::{
|
||||
error::{Error as WindmillError, Result as WindmillResult},
|
||||
triggers::TriggerKind,
|
||||
utils::empty_as_none,
|
||||
},
|
||||
};
|
||||
use axum::Router;
|
||||
use crate::db::DB;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::jobs::QueuedJob;
|
||||
|
||||
#[derive(sqlx::Type, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
#[sqlx(type_name = "DELIVERY_MODE", rename_all = "lowercase")]
|
||||
#[allow(unused)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum DeliveryType {
|
||||
Pull,
|
||||
Push,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl Default for DeliveryType {
|
||||
fn default() -> Self {
|
||||
Self::Pull
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize, Debug)]
|
||||
#[allow(unused)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct PushConfig {
|
||||
#[serde(deserialize_with = "empty_as_none")]
|
||||
route_path: Option<String>,
|
||||
#[serde(deserialize_with = "empty_as_none")]
|
||||
audience: Option<String>,
|
||||
authenticate: bool,
|
||||
base_endpoint: String,
|
||||
}
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct CreateUpdateConfig {
|
||||
pub delivery_type: DeliveryType,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub subscription_id: Option<String>,
|
||||
pub delivery_config: Option<SqlxJson<PushConfig>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct ExistingGcpSubscription {
|
||||
pub subscription_id: String,
|
||||
pub base_endpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, sqlx::Type)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[sqlx(type_name = "GCP_SUBSCRIPTION_MODE", rename_all = "snake_case")]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum SubscriptionMode {
|
||||
Existing,
|
||||
CreateUpdate,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
crate::gcp_triggers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn start_consuming_gcp_pubsub_event(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
// implementation is not open source
|
||||
db: DB,
|
||||
killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) {
|
||||
crate::gcp_triggers_ee::start_consuming_gcp_pubsub_event(db, killpill_rx)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn manage_google_subscription(
|
||||
_authed: ApiAuthed,
|
||||
_db: &DB,
|
||||
_workspace_id: &str,
|
||||
_gcp_resource_path: &str,
|
||||
_path: &str,
|
||||
_topic_id: &str,
|
||||
_subscription_id: &mut Option<String>,
|
||||
_base_endpoint: &mut Option<String>,
|
||||
_subscription_mode: SubscriptionMode,
|
||||
_create_update_config: Option<CreateUpdateConfig>,
|
||||
_trigger_mode: bool,
|
||||
_is_flow: bool,
|
||||
) -> WindmillResult<CreateUpdateConfig> {
|
||||
Ok(CreateUpdateConfig::default())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn process_google_push_request(
|
||||
_headers: HeaderMap,
|
||||
_request: Request,
|
||||
) -> Result<(String, HashMap<String, Box<RawValue>>), WindmillError> {
|
||||
Ok((String::new(), HashMap::new()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_jwt_token(
|
||||
_db: &DB,
|
||||
_user_db: UserDB,
|
||||
_authed: ApiAuthed,
|
||||
_headers: &HeaderMap,
|
||||
_gcp_resource_path: &str,
|
||||
_workspace_id: &str,
|
||||
_delivery_config: &PushConfig,
|
||||
) -> Result<(), windmill_common::error::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn gcp_push_route_handler() -> Router {
|
||||
Router::new()
|
||||
crate::gcp_triggers_ee::gcp_push_route_handler()
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize, Debug)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct GcpTrigger {
|
||||
pub gcp_resource_path: String,
|
||||
pub subscription_id: String,
|
||||
pub delivery_type: DeliveryType,
|
||||
pub delivery_config: Option<SqlxJson<PushConfig>>,
|
||||
pub subscription_mode: SubscriptionMode,
|
||||
pub topic_id: String,
|
||||
pub path: String,
|
||||
pub script_path: String,
|
||||
pub is_flow: bool,
|
||||
pub workspace_id: String,
|
||||
pub edited_by: String,
|
||||
pub email: String,
|
||||
pub edited_at: chrono::DateTime<chrono::Utc>,
|
||||
pub extra_perms: Option<serde_json::Value>,
|
||||
pub error: Option<String>,
|
||||
pub server_id: Option<String>,
|
||||
pub last_server_ping: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub enabled: bool,
|
||||
pub async fn manage_google_subscription(
|
||||
path: String,
|
||||
trigger: GcpTrigger,
|
||||
operation: String,
|
||||
w_id: String,
|
||||
db: DB,
|
||||
) -> anyhow::Result<()> {
|
||||
crate::gcp_triggers_ee::manage_google_subscription(path, trigger, operation, w_id, db).await
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl TriggerJobArgs<String> for GcpTrigger {
|
||||
fn v1_payload_fn(payload: String) -> HashMap<String, Box<RawValue>> {
|
||||
HashMap::from([("payload".to_string(), to_raw_value(&payload))])
|
||||
}
|
||||
|
||||
fn trigger_kind() -> TriggerKind {
|
||||
TriggerKind::Gcp
|
||||
}
|
||||
pub async fn process_google_push_request(
|
||||
workspace_id: String,
|
||||
trigger_token: String,
|
||||
message: HashMap<String, serde_json::Value>,
|
||||
db: DB,
|
||||
) -> anyhow::Result<Option<QueuedJob>> {
|
||||
crate::gcp_triggers_ee::process_google_push_request(workspace_id, trigger_token, message, db).await
|
||||
}
|
||||
|
||||
pub async fn validate_jwt_token(
|
||||
token: String,
|
||||
audience: String,
|
||||
) -> anyhow::Result<()> {
|
||||
crate::gcp_triggers_ee::validate_jwt_token(token, audience).await
|
||||
}
|
||||
|
||||
pub use crate::gcp_triggers_ee::CreateUpdateConfig;
|
||||
pub use crate::gcp_triggers_ee::DeliveryType;
|
||||
pub use crate::gcp_triggers_ee::ExistingGcpSubscription;
|
||||
pub use crate::gcp_triggers_ee::GcpTrigger;
|
||||
pub use crate::gcp_triggers_ee::PushConfig;
|
||||
pub use crate::gcp_triggers_ee::SubscriptionMode;
|
||||
9
backend/windmill-api/src/git_sync_ee.rs
Normal file
9
backend/windmill-api/src/git_sync_ee.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use axum::routing::Router;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::git_sync_ee::*;
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::routing::Router;
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
crate::git_sync_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
crate::git_sync_ee::global_service()
|
||||
}
|
||||
@@ -14,7 +14,7 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::{
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
|
||||
pub struct RawHttpTriggerArgs(pub RawWebhookArgs);
|
||||
|
||||
#[derive(Serialize, Deserialize, sqlx::Type, Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, sqlx::Type, Debug, Clone, Hash, Eq, PartialEq)]
|
||||
#[sqlx(type_name = "HTTP_METHOD", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum HttpMethod {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "http_trigger")]
|
||||
use crate::http_trigger_args::{HttpMethod, RawHttpTriggerArgs};
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::job_helpers_oss::get_workspace_s3_resource;
|
||||
use crate::job_helpers_ee::get_workspace_s3_resource;
|
||||
use crate::resources::try_get_resource_from_db_as;
|
||||
use crate::trigger_helpers::{get_runnable_format, RunnableId};
|
||||
use crate::utils::{non_empty_str, ExpiringCacheEntry};
|
||||
@@ -14,7 +14,6 @@ use crate::{
|
||||
},
|
||||
users::fetch_api_authed,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use axum::response::Response;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
@@ -29,14 +28,13 @@ use quick_cache::sync::Cache;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::prelude::FromRow;
|
||||
use sqlx::PgConnection;
|
||||
use sqlx::PgTransaction;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tower_http::cors::CorsLayer;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_common::error::{Error, Result as WindmillResult};
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::error::Error;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::build_object_store_client;
|
||||
use windmill_common::{
|
||||
@@ -81,7 +79,6 @@ pub fn routes_global_service() -> Router {
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(create_trigger))
|
||||
.route("/create_many", post(create_many_http_trigger))
|
||||
.route("/list", get(list_triggers))
|
||||
.route("/get/*path", get(get_trigger))
|
||||
.route("/update/*path", post(update_trigger))
|
||||
@@ -276,7 +273,7 @@ async fn get_trigger(
|
||||
fn validate_authentication_method(
|
||||
authentication_method: AuthenticationMethod,
|
||||
raw_string: Option<bool>,
|
||||
) -> WindmillResult<()> {
|
||||
) -> error::Result<()> {
|
||||
match (authentication_method, raw_string) {
|
||||
(AuthenticationMethod::CustomScript, raw) if !raw.unwrap_or(false) == true => {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -290,21 +287,57 @@ fn validate_authentication_method(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn increase_trigger_version(tx: &mut PgConnection) -> WindmillResult<()> {
|
||||
async fn increase_trigger_version_and_commit(mut tx: PgTransaction<'_>) -> error::Result<()> {
|
||||
sqlx::query!("SELECT nextval('http_trigger_version_seq')",)
|
||||
.fetch_one(tx)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_trigger_inner(
|
||||
tx: &mut PgConnection,
|
||||
w_id: &str,
|
||||
authed: &ApiAuthed,
|
||||
new_http_trigger: &NewTrigger,
|
||||
route_path_key: &str,
|
||||
) -> WindmillResult<()> {
|
||||
async fn create_trigger(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ct): Json<NewTrigger>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
if !VALID_ROUTE_PATH_RE.is_match(&ct.route_path) {
|
||||
return Err(error::Error::BadRequest("Invalid route path".to_string()));
|
||||
}
|
||||
|
||||
validate_authentication_method(ct.authentication_method, ct.raw_string)?;
|
||||
|
||||
// route path key is extracted from the route path to check for uniqueness
|
||||
// it replaces /?:{key} with :key
|
||||
// it will also remove the leading / if present, not an issue as we only allow : after slashes
|
||||
let route_path_key = ROUTE_PATH_KEY_RE.replace_all(&ct.route_path, ":key");
|
||||
let exists = route_path_key_exists(
|
||||
&route_path_key,
|
||||
&ct.http_method,
|
||||
&w_id,
|
||||
None,
|
||||
ct.workspaced_route,
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
if exists {
|
||||
return Err(error::Error::BadRequest(
|
||||
"A route already exists with this path".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if *CLOUD_HOSTED && (ct.is_static_website || ct.static_asset_config.is_some()) {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Static website and static asset are not supported on cloud".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO http_trigger (
|
||||
@@ -332,199 +365,51 @@ async fn create_trigger_inner(
|
||||
)
|
||||
"#,
|
||||
w_id,
|
||||
new_http_trigger.path,
|
||||
new_http_trigger.route_path,
|
||||
ct.path,
|
||||
ct.route_path,
|
||||
&route_path_key,
|
||||
new_http_trigger.workspaced_route,
|
||||
new_http_trigger.authentication_resource_path,
|
||||
new_http_trigger.wrap_body.unwrap_or(false),
|
||||
new_http_trigger.raw_string.unwrap_or(false),
|
||||
new_http_trigger.script_path,
|
||||
new_http_trigger.is_flow,
|
||||
new_http_trigger.is_async,
|
||||
new_http_trigger.authentication_method as _,
|
||||
new_http_trigger.http_method as _,
|
||||
new_http_trigger.static_asset_config as _,
|
||||
ct.workspaced_route,
|
||||
ct.authentication_resource_path,
|
||||
ct.wrap_body.unwrap_or(false),
|
||||
ct.raw_string.unwrap_or(false),
|
||||
ct.script_path,
|
||||
ct.is_flow,
|
||||
ct.is_async,
|
||||
ct.authentication_method as _,
|
||||
ct.http_method as _,
|
||||
ct.static_asset_config as _,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
new_http_trigger.is_static_website
|
||||
ct.is_static_website
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
authed,
|
||||
&authed,
|
||||
"http_triggers.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
Some(new_http_trigger.path.as_str()),
|
||||
Some(ct.path.as_str()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
increase_trigger_version(tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_no_duplicates<'trigger>(
|
||||
new_http_triggers: &[NewTrigger],
|
||||
route_path_key: &[Cow<'trigger, str>],
|
||||
) -> Result<(), Error> {
|
||||
let mut seen = HashSet::with_capacity(new_http_triggers.len());
|
||||
|
||||
for (i, trigger) in new_http_triggers.iter().enumerate() {
|
||||
if !seen.insert((&route_path_key[i], trigger.http_method, trigger.workspaced_route)) {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"Duplicate HTTP route detected: '{}'. Each HTTP route must have a unique 'route_path'.",
|
||||
&trigger.route_path
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_many_http_trigger(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(new_http_triggers): Json<Vec<NewTrigger>>,
|
||||
) -> WindmillResult<(StatusCode, String)> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let error_wrapper = |path: &str, error: Error| -> Error {
|
||||
anyhow!(
|
||||
"Error occurred for HTTP route at route path: {}, error: {}",
|
||||
path,
|
||||
error
|
||||
)
|
||||
.into()
|
||||
};
|
||||
|
||||
let mut route_path_keys = Vec::with_capacity(new_http_triggers.len());
|
||||
|
||||
for new_http_trigger in new_http_triggers.iter() {
|
||||
let route_path_key = validate_http_trigger(&db, &w_id, new_http_trigger)
|
||||
.await
|
||||
.map_err(|err| error_wrapper(&new_http_trigger.route_path, err))?;
|
||||
|
||||
route_path_keys.push(route_path_key);
|
||||
}
|
||||
|
||||
check_no_duplicates(&new_http_triggers, &route_path_keys)?;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
for (i, new_http_trigger) in new_http_triggers.iter().enumerate() {
|
||||
create_trigger_inner(
|
||||
&mut tx,
|
||||
&w_id,
|
||||
&authed,
|
||||
new_http_trigger,
|
||||
&route_path_keys[i],
|
||||
)
|
||||
.await
|
||||
.map_err(|err| error_wrapper(&new_http_trigger.route_path, err))?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
for http_trigger in new_http_triggers.into_iter() {
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::HttpTrigger { path: http_trigger.path.clone() },
|
||||
Some(format!("HTTP route '{}' created", http_trigger.path)),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((StatusCode::CREATED, format!("Created all HTTP routes")))
|
||||
}
|
||||
|
||||
async fn validate_http_trigger<'trigger>(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
new_http_trigger: &'trigger NewTrigger,
|
||||
) -> WindmillResult<Cow<'trigger, str>> {
|
||||
if !VALID_ROUTE_PATH_RE.is_match(&new_http_trigger.route_path) {
|
||||
return Err(error::Error::BadRequest("Invalid route path".to_string()));
|
||||
}
|
||||
|
||||
validate_authentication_method(
|
||||
new_http_trigger.authentication_method,
|
||||
new_http_trigger.raw_string,
|
||||
)?;
|
||||
|
||||
// route path key is extracted from the route path to check for uniqueness
|
||||
// it replaces /?:{key} with :key
|
||||
// it will also remove the leading / if present, not an issue as we only allow : after slashes
|
||||
let route_path_key = ROUTE_PATH_KEY_RE.replace_all(&new_http_trigger.route_path, ":key");
|
||||
|
||||
let exists = route_path_key_exists(
|
||||
&route_path_key,
|
||||
&new_http_trigger.http_method,
|
||||
&w_id,
|
||||
None,
|
||||
new_http_trigger.workspaced_route,
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if exists {
|
||||
return Err(error::Error::BadRequest(
|
||||
"A route already exists with this path".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if *CLOUD_HOSTED
|
||||
&& (new_http_trigger.is_static_website || new_http_trigger.static_asset_config.is_some())
|
||||
{
|
||||
return Err(error::Error::BadRequest(
|
||||
"Static website and static asset are not supported on cloud".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(route_path_key)
|
||||
}
|
||||
|
||||
async fn create_trigger(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(new_http_trigger): Json<NewTrigger>,
|
||||
) -> WindmillResult<(StatusCode, String)> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let route_path_key = validate_http_trigger(&db, &w_id, &new_http_trigger).await?;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let http_trigger_path = new_http_trigger.path.clone();
|
||||
|
||||
create_trigger_inner(&mut tx, &w_id, &authed, &new_http_trigger, &route_path_key).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
increase_trigger_version_and_commit(tx).await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::HttpTrigger { path: new_http_trigger.path.clone() },
|
||||
Some(format!("HTTP route '{}' created", new_http_trigger.path)),
|
||||
windmill_git_sync::DeployedObject::HttpTrigger { path: ct.path.clone() },
|
||||
Some(format!("HTTP trigger '{}' created", ct.path)),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((StatusCode::CREATED, format!("{}", http_trigger_path)))
|
||||
Ok((StatusCode::CREATED, format!("{}", ct.path)))
|
||||
}
|
||||
|
||||
async fn update_trigger(
|
||||
@@ -533,9 +418,8 @@ async fn update_trigger(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(ct): Json<EditTrigger>,
|
||||
) -> WindmillResult<String> {
|
||||
) -> error::Result<String> {
|
||||
let path = path.to_path();
|
||||
|
||||
if *CLOUD_HOSTED && (ct.is_static_website || ct.static_asset_config.is_some()) {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Static website and static asset are not supported on cloud".to_string(),
|
||||
@@ -679,9 +563,7 @@ async fn update_trigger(
|
||||
)
|
||||
.await?;
|
||||
|
||||
increase_trigger_version(&mut tx).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
increase_trigger_version_and_commit(tx).await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
@@ -689,7 +571,7 @@ async fn update_trigger(
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::HttpTrigger { path: ct.path.clone() },
|
||||
Some(format!("HTTP route '{}' updated", ct.path)),
|
||||
Some(format!("HTTP trigger '{}' updated", ct.path)),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
@@ -702,7 +584,7 @@ async fn delete_trigger(
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> WindmillResult<String> {
|
||||
) -> error::Result<String> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let path = path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
@@ -727,9 +609,7 @@ async fn delete_trigger(
|
||||
)
|
||||
.await?;
|
||||
|
||||
increase_trigger_version(&mut tx).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
increase_trigger_version_and_commit(tx).await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
@@ -737,12 +617,12 @@ async fn delete_trigger(
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::HttpTrigger { path: path.to_string() },
|
||||
Some(format!("HTTP route '{}' deleted", path)),
|
||||
Some(format!("HTTP trigger '{}' deleted", path)),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(format!("HTTP route {path} deleted"))
|
||||
Ok(format!("HTTP trigger {path} deleted"))
|
||||
}
|
||||
|
||||
async fn exists_trigger(
|
||||
@@ -780,7 +660,7 @@ async fn route_path_key_exists(
|
||||
trigger_path: Option<&str>,
|
||||
workspaced_route: Option<bool>,
|
||||
db: &DB,
|
||||
) -> WindmillResult<bool> {
|
||||
) -> error::Result<bool> {
|
||||
let exists = if *CLOUD_HOSTED {
|
||||
sqlx::query_scalar!(
|
||||
r#"
|
||||
@@ -981,7 +861,7 @@ pub async fn refresh_routers(db: &DB) -> Result<(bool, RwLockReadGuard<'_, Route
|
||||
.insert(format!("{}/*wm_subpath", full_path), trigger.clone())
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!(
|
||||
"Failed to consider HTTP route {}/*wm_subpath: {:?}",
|
||||
"Failed to consider http trigger route {}/*wm_subpath: {:?}",
|
||||
full_path,
|
||||
e,
|
||||
);
|
||||
@@ -990,7 +870,11 @@ pub async fn refresh_routers(db: &DB) -> Result<(bool, RwLockReadGuard<'_, Route
|
||||
router
|
||||
.insert(full_path.clone(), trigger.clone())
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!("Failed to consider HTTP route {}: {:?}", full_path, e,);
|
||||
tracing::warn!(
|
||||
"Failed to consider http trigger route {}: {:?}",
|
||||
full_path,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1014,7 +898,7 @@ async fn get_http_route_trigger(
|
||||
db: &DB,
|
||||
user_db: UserDB,
|
||||
method: &http::Method,
|
||||
) -> WindmillResult<(TriggerRoute, String, HashMap<String, String>, ApiAuthed)> {
|
||||
) -> error::Result<(TriggerRoute, String, HashMap<String, String>, ApiAuthed)> {
|
||||
let http_method: HttpMethod = method.try_into()?;
|
||||
|
||||
let requested_path = format!("/{}", route_path);
|
||||
@@ -1063,11 +947,11 @@ async fn get_http_route_trigger(
|
||||
);
|
||||
let exists = match HTTP_ACCESS_CACHE.get(&cache_key) {
|
||||
Some(cache_entry) if cache_entry.expiry > std::time::Instant::now() => {
|
||||
tracing::debug!("HTTP access cache hit for route {}", trigger.path);
|
||||
tracing::debug!("HTTP access cache hit for trigger {}", trigger.path);
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
tracing::debug!("HTTP access cache miss for route {}", trigger.path);
|
||||
tracing::debug!("HTTP access cache miss for trigger {}", trigger.path);
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let exists = sqlx::query_scalar!(
|
||||
r#"
|
||||
@@ -1118,7 +1002,7 @@ async fn get_http_route_trigger(
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
&db,
|
||||
Some(username_override.unwrap_or(format!("HTTP-{}", trigger.path))),
|
||||
Some(username_override.unwrap_or(format!("http-{}", trigger.path))),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1146,14 +1030,6 @@ async fn route_job(
|
||||
.await
|
||||
.map_err(|e| e.into_response())?;
|
||||
|
||||
if trigger.script_path.is_empty() && trigger.static_asset_config.is_none() {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"Script path of HTTP route at path: {} must not be empty",
|
||||
trigger.path
|
||||
))
|
||||
.into_response());
|
||||
}
|
||||
|
||||
let args = args
|
||||
.process_args(
|
||||
&authed,
|
||||
@@ -1190,11 +1066,11 @@ async fn route_job(
|
||||
|
||||
let authentication_method = match HTTP_AUTH_CACHE.get(&cache_key) {
|
||||
Some(cache_entry) if cache_entry.expiry > std::time::Instant::now() => {
|
||||
tracing::debug!("HTTP auth method cache hit for route {}", trigger.path);
|
||||
tracing::debug!("HTTP auth method cache hit for trigger {}", trigger.path);
|
||||
cache_entry.value
|
||||
}
|
||||
_ => {
|
||||
tracing::debug!("HTTP auth method cache miss for route {}", trigger.path);
|
||||
tracing::debug!("HTTP auth method cache miss for trigger {}", trigger.path);
|
||||
let auth_method = try_get_resource_from_db_as::<
|
||||
crate::http_trigger_auth::AuthenticationMethod,
|
||||
>(
|
||||
|
||||
9
backend/windmill-api/src/indexer_ee.rs
Normal file
9
backend/windmill-api/src/indexer_ee.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::indexer_ee::*;
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
crate::indexer_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
crate::indexer_ee::global_service()
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::inkeep_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
121
backend/windmill-api/src/job_helpers_ee.rs
Normal file
121
backend/windmill-api/src/job_helpers_ee.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use axum::Router;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::s3_helpers::StorageResourceType;
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
#[cfg(feature = "parquet")]
|
||||
use object_store::{ObjectStore, PutMultipartOpts};
|
||||
#[cfg(feature = "parquet")]
|
||||
use std::sync::Arc;
|
||||
use windmill_common::error;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use bytes::Bytes;
|
||||
#[cfg(feature = "parquet")]
|
||||
use futures::Stream;
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use axum::response::Response;
|
||||
#[cfg(feature = "parquet")]
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UploadFileResponse {
|
||||
pub file_key: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoadImagePreviewQuery {
|
||||
#[allow(dead_code)]
|
||||
pub file_key: String,
|
||||
#[allow(dead_code)]
|
||||
pub storage: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DownloadFileQuery {
|
||||
#[allow(dead_code)]
|
||||
pub file_key: String,
|
||||
#[allow(dead_code)]
|
||||
pub storage: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
pub s3_resource_path: Option<String>,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn get_workspace_s3_resource<'c>(
|
||||
_authed: &ApiAuthed,
|
||||
_db: &DB,
|
||||
_user_db: Option<UserDB>,
|
||||
_token: &str,
|
||||
_w_id: &str,
|
||||
_storage: Option<String>,
|
||||
) -> windmill_common::error::Result<(Option<bool>, Option<ObjectStoreResource>)> {
|
||||
// implementation is not open source
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
pub fn get_random_file_name(_file_extension: Option<String>) -> String {
|
||||
unimplemented!("Not implemented in Windmill's Open Source repository")
|
||||
}
|
||||
|
||||
pub async fn get_s3_resource<'c>(
|
||||
_authed: &ApiAuthed,
|
||||
_db: &DB,
|
||||
_user_db: Option<UserDB>,
|
||||
_token: &str,
|
||||
_w_id: &str,
|
||||
_resource_path: &str,
|
||||
_resource_type: Option<StorageResourceType>,
|
||||
_job_id: Option<Uuid>,
|
||||
) -> error::Result<ObjectStoreResource> {
|
||||
Err(error::Error::internal_err(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn upload_file_from_req(
|
||||
_s3_client: Arc<dyn ObjectStore>,
|
||||
_file_key: &str,
|
||||
_req: axum::extract::Request,
|
||||
_options: PutMultipartOpts,
|
||||
) -> error::Result<()> {
|
||||
Err(error::Error::internal_err(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn upload_file_internal(
|
||||
_s3_client: Arc<dyn ObjectStore>,
|
||||
_file_key: &str,
|
||||
_stream: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
_options: PutMultipartOpts,
|
||||
) -> error::Result<()> {
|
||||
Err(error::Error::internal_err(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn download_s3_file_internal(
|
||||
_authed: ApiAuthed,
|
||||
_db: &DB,
|
||||
_user_db: Option<UserDB>,
|
||||
_token: &str,
|
||||
_w_id: &str,
|
||||
_query: DownloadFileQuery,
|
||||
) -> error::Result<Response> {
|
||||
Err(error::Error::internal_err(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user