Compare commits
116 Commits
v1.482.1
...
rf/orderBy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
461306ab04 | ||
|
|
062e904141 | ||
|
|
b0ba8350f7 | ||
|
|
2adafcd265 | ||
|
|
9bdd301f52 | ||
|
|
afc9cf5940 | ||
|
|
4fd0561239 | ||
|
|
815c3b6d00 | ||
|
|
36da8aec08 | ||
|
|
d2ad2cdf33 | ||
|
|
6b2ba3f261 | ||
|
|
d103f5a466 | ||
|
|
7eb9d7d46c | ||
|
|
8ad730b290 | ||
|
|
ec701a9ee7 | ||
|
|
9c55040e47 | ||
|
|
41c15fc78a | ||
|
|
09169784bd | ||
|
|
4c660a8869 | ||
|
|
dfb947ff37 | ||
|
|
257040ab28 | ||
|
|
dda92b5491 | ||
|
|
18dced3c74 | ||
|
|
c641550eee | ||
|
|
fbdaf28fb5 | ||
|
|
75ee10ea65 | ||
|
|
f120f4ac4b | ||
|
|
46a3c29c9f | ||
|
|
1ab41603f4 | ||
|
|
60018aadf6 | ||
|
|
193186ae75 | ||
|
|
e298c8eea5 | ||
|
|
f662cf5d75 | ||
|
|
418c36ed62 | ||
|
|
0b03b06144 | ||
|
|
486ad2a2b5 | ||
|
|
36b39f2c91 | ||
|
|
99c69418e7 | ||
|
|
cf77ff088b | ||
|
|
2fc7960b62 | ||
|
|
2ea42a626a | ||
|
|
71d400e4d1 | ||
|
|
35b69da25c | ||
|
|
eadae95a42 | ||
|
|
90ebcfb51e | ||
|
|
2b8af4e314 | ||
|
|
b67de13b78 | ||
|
|
12c32026e5 | ||
|
|
654e3e857e | ||
|
|
72547437fe | ||
|
|
c599857e4a | ||
|
|
1b1691837a | ||
|
|
03553f7f49 | ||
|
|
a7b2b51444 | ||
|
|
a74815f143 | ||
|
|
02ab233a85 | ||
|
|
a71f92db35 | ||
|
|
5e1f47ae29 | ||
|
|
97ae0d80df | ||
|
|
4bb9d64d4f | ||
|
|
42edbfac66 | ||
|
|
7264618ad8 | ||
|
|
8435eb3adf | ||
|
|
ac129d9b65 | ||
|
|
6e833cbf2f | ||
|
|
58140cde2f | ||
|
|
2b5dfcfb25 | ||
|
|
dc5c8d8c5f | ||
|
|
1625524311 | ||
|
|
fdd1642ce1 | ||
|
|
c4148d7756 | ||
|
|
b199a77d48 | ||
|
|
a34ac4fa24 | ||
|
|
8d4d2db507 | ||
|
|
c41c1eb587 | ||
|
|
b146de281b | ||
|
|
60a848af48 | ||
|
|
494d662b2d | ||
|
|
0a866da274 | ||
|
|
e316dbd9bd | ||
|
|
444a6abad6 | ||
|
|
128f6c1383 | ||
|
|
855a079744 | ||
|
|
2e25f1559e | ||
|
|
42514f2b1d | ||
|
|
2620300ce9 | ||
|
|
5bca8f60e9 | ||
|
|
80658a4b21 | ||
|
|
d4f4bb0295 | ||
|
|
941adea42a | ||
|
|
989a4f7e61 | ||
|
|
2bc929b4c6 | ||
|
|
0fdf5e4056 | ||
|
|
8006be5883 | ||
|
|
2e3de4e6b0 | ||
|
|
c769da6022 | ||
|
|
3c68fef2ab | ||
|
|
e88909aa93 | ||
|
|
2bf070ee5c | ||
|
|
e5595e41b5 | ||
|
|
236762641f | ||
|
|
64c72b6fce | ||
|
|
02a25ba62e | ||
|
|
ab3ff1d701 | ||
|
|
8f7854dc63 | ||
|
|
cdbe534a5d | ||
|
|
6a9bdfd3bd | ||
|
|
b9bb071a8a | ||
|
|
5b123b01a1 | ||
|
|
da13014c41 | ||
|
|
58fa4c8006 | ||
|
|
34b307b2be | ||
|
|
2655da0796 | ||
|
|
2fa0717ee0 | ||
|
|
081145726a | ||
|
|
4198a574d4 |
2
.env
2
.env
@@ -10,4 +10,4 @@ WM_IMAGE=ghcr.io/windmill-labs/windmill:main
|
||||
|
||||
# To rotate logs, set the following variables:
|
||||
#LOG_MAX_SIZE=10m
|
||||
#LOG_MAX_FILE=3
|
||||
#LOG_MAX_FILE=3
|
||||
2
.github/change-versions.sh
vendored
2
.github/change-versions.sh
vendored
@@ -24,4 +24,4 @@ sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
|
||||
sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock
|
||||
|
||||
cd ${root_dirpath}/frontend && npm i --package-lock-only
|
||||
cd ${root_dirpath}/frontend && npm i --package-lock-only --ignore-scripts
|
||||
|
||||
7
.github/workflows/build-publish-rh-image.yml
vendored
7
.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
|
||||
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
|
||||
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 }}
|
||||
@@ -111,8 +111,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: RHEL9-amd64 build
|
||||
path:
|
||||
${{ steps.extract-ee-amd64.outputs.destination
|
||||
path: ${{ steps.extract-ee-amd64.outputs.destination
|
||||
}}/windmill-ee-amd64-rhel9
|
||||
|
||||
# - uses: actions/upload-artifact@v4
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
2
.github/workflows/docker-image-rpi4.yml
vendored
2
.github/workflows/docker-image-rpi4.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,license,http_trigger,zip,oauth2,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages
|
||||
features=embedding,parquet,openidconnect,license,http_trigger,zip,oauth2,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
|
||||
26
.github/workflows/docker-image.yml
vendored
26
.github/workflows/docker-image.yml
vendored
@@ -1,10 +1,8 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME:
|
||||
${{ github.event_name != 'pull_request' && github.event_name !=
|
||||
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.event_name !=
|
||||
'workflow_dispatch' && github.repository || 'windmill-labs/windmill-test' }}
|
||||
DEV_SHA:
|
||||
${{ github.event_name != 'pull_request' && github.event_name !=
|
||||
DEV_SHA: ${{ github.event_name != 'pull_request' && github.event_name !=
|
||||
'workflow_dispatch' && 'dev' || github.event.inputs.tag || github.sha }}
|
||||
name: Build windmill:main
|
||||
on:
|
||||
@@ -40,8 +38,7 @@ permissions: write-all
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubicloud
|
||||
if:
|
||||
(github.event_name != 'workflow_dispatch') || (github.event.inputs &&
|
||||
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs &&
|
||||
!github.event.inputs.ee)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -95,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
|
||||
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 }}
|
||||
@@ -157,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
|
||||
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 }}
|
||||
@@ -315,7 +312,7 @@ jobs:
|
||||
needs: [run_integration_test, build]
|
||||
if:
|
||||
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
|
||||
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -355,7 +352,7 @@ jobs:
|
||||
verify_ee_image_vulnerabilities:
|
||||
runs-on: ubicloud
|
||||
needs: [tag_latest_ee]
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -397,8 +394,7 @@ jobs:
|
||||
build_ee_nsjail:
|
||||
needs: [build_ee]
|
||||
runs-on: ubicloud
|
||||
if:
|
||||
(github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
|
||||
if: (github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -437,7 +433,7 @@ jobs:
|
||||
run: |
|
||||
sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail
|
||||
cat ./docker/DockerfileNsjail | grep "FROM"
|
||||
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
@@ -451,12 +447,10 @@ jobs:
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
|
||||
publish_ecr_s3:
|
||||
needs: [build_ee_nsjail]
|
||||
runs-on: ubicloud-standard-2-arm
|
||||
if:
|
||||
(github.event_name != 'pull_request') && (github.event_name !=
|
||||
if: (github.event_name != 'pull_request') && (github.event_name !=
|
||||
'workflow_dispatch')
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
112
CHANGELOG.md
112
CHANGELOG.md
@@ -1,5 +1,117 @@
|
||||
# Changelog
|
||||
|
||||
## [1.486.1](https://github.com/windmill-labs/windmill/compare/v1.486.0...v1.486.1) (2025-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve MultiSelectWrapper behavior ([36da8ae](https://github.com/windmill-labs/windmill/commit/36da8aec080742e13f23e1dee12b3954947f53dd))
|
||||
|
||||
## [1.486.0](https://github.com/windmill-labs/windmill/compare/v1.485.3...v1.486.0) (2025-05-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add run now directly on schedule drawer and duplicate schedule option ([#5674](https://github.com/windmill-labs/windmill/issues/5674)) ([dfb947f](https://github.com/windmill-labs/windmill/commit/dfb947ff37c688f54a32de5aa3c5c3d142cb80f4))
|
||||
* Database Manager ([#5586](https://github.com/windmill-labs/windmill/issues/5586)) ([41c15fc](https://github.com/windmill-labs/windmill/commit/41c15fc78aaf844c559d3d6c772e04ecce436e9d))
|
||||
* Integrate MCP with hub ([#5685](https://github.com/windmill-labs/windmill/issues/5685)) ([ec701a9](https://github.com/windmill-labs/windmill/commit/ec701a9ee74c9d890b54234362392deca63a77c7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Ai Chat: do not send tools if empty + respond even if tool fails ([#5692](https://github.com/windmill-labs/windmill/issues/5692)) ([9c55040](https://github.com/windmill-labs/windmill/commit/9c55040e47e76af8b7e2864b82fa30505545dcb5))
|
||||
* do not track relative deps for scripts with raw defined deps from CLI ([#5696](https://github.com/windmill-labs/windmill/issues/5696)) ([7eb9d7d](https://github.com/windmill-labs/windmill/commit/7eb9d7d46cb48ae69a3fd3ff852a57abae450a3b))
|
||||
* improve CLI file scanning performances ([0916978](https://github.com/windmill-labs/windmill/commit/09169784bd2d0ab7acf5f40dc86f36f1cae967b7))
|
||||
|
||||
## [1.485.3](https://github.com/windmill-labs/windmill/compare/v1.485.2...v1.485.3) (2025-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve performance of background cleanup monitoring operations ([18dced3](https://github.com/windmill-labs/windmill/commit/18dced3c748cd5305f0934b26e50d69899563723))
|
||||
|
||||
## [1.485.2](https://github.com/windmill-labs/windmill/compare/v1.485.1...v1.485.2) (2025-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve agent workers for deployed scripts ([60018aa](https://github.com/windmill-labs/windmill/commit/60018aadf62cecadf111e019d3600513a89810f1))
|
||||
* make `#(extra_)requirements:` work better with pins ([#5680](https://github.com/windmill-labs/windmill/issues/5680)) ([1ab4160](https://github.com/windmill-labs/windmill/commit/1ab41603f4fd1526d0c944396ef250b184aed1f4))
|
||||
* **python:** handle better relative imports with requirements or extra_requirements ([f662cf5](https://github.com/windmill-labs/windmill/commit/f662cf5d75beed8fd114ba171cbe0fa8e4b2773f))
|
||||
|
||||
## [1.485.1](https://github.com/windmill-labs/windmill/compare/v1.485.0...v1.485.1) (2025-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve mcp mode api ([cf77ff0](https://github.com/windmill-labs/windmill/commit/cf77ff088b8382b861113120589de58f7cf241d0))
|
||||
* MCP handle long names + invalid char in prop key + fix for not found resource type ([#5668](https://github.com/windmill-labs/windmill/issues/5668)) ([eadae95](https://github.com/windmill-labs/windmill/commit/eadae95a42d679bf8792bdefd8b9d19dbcbc4b57))
|
||||
* skip_flow_update for dependency tracking table ([#5670](https://github.com/windmill-labs/windmill/issues/5670)) ([35b69da](https://github.com/windmill-labs/windmill/commit/35b69da25c5bd17deff5a54b635e9150cb865cc0))
|
||||
|
||||
## [1.485.0](https://github.com/windmill-labs/windmill/compare/v1.484.0...v1.485.0) (2025-04-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add universal search to object viewer ([7254743](https://github.com/windmill-labs/windmill/commit/72547437fead0a071fceac27dae8628cdcae6a3e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add svelte 5 boundaries to app components to contain errors ([1b16918](https://github.com/windmill-labs/windmill/commit/1b1691837a7e6b88afbacf7d88c14ca5e475b493))
|
||||
* Fix object handling on some MCP clients + better frontend for MCP ([#5663](https://github.com/windmill-labs/windmill/issues/5663)) ([12c3202](https://github.com/windmill-labs/windmill/commit/12c32026e5879a65fc0f1cc9f2481087c4b95111))
|
||||
|
||||
## [1.484.0](https://github.com/windmill-labs/windmill/compare/v1.483.2...v1.484.0) (2025-04-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add MCP endpoints ([#5639](https://github.com/windmill-labs/windmill/issues/5639)) ([a34ac4f](https://github.com/windmill-labs/windmill/commit/a34ac4fa24c2a5482e45724e76316d57f64f7040))
|
||||
* Add MCP only mode ([#5661](https://github.com/windmill-labs/windmill/issues/5661)) ([1625524](https://github.com/windmill-labs/windmill/commit/162552431138d68002c7060cad4ae31f1ec4c69c))
|
||||
* Ansible improvements (vault, roles and git repos) ([#5655](https://github.com/windmill-labs/windmill/issues/5655)) ([fdd1642](https://github.com/windmill-labs/windmill/commit/fdd1642ce10866da1d8d373bda44f050e2e0f403))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check for valid teams_channel config when saving critical alerts settings ([#5660](https://github.com/windmill-labs/windmill/issues/5660)) ([dc5c8d8](https://github.com/windmill-labs/windmill/commit/dc5c8d8c5f8577b7ded3da1d684cdb735fa7a936))
|
||||
* Fix CI for MCP + optimization ([#5657](https://github.com/windmill-labs/windmill/issues/5657)) ([b199a77](https://github.com/windmill-labs/windmill/commit/b199a77d486c5bfd086ca73a58a14bb747e386b5))
|
||||
* fix token creation after mcp mode change to make it non workspace specific ([2b5dfcf](https://github.com/windmill-labs/windmill/commit/2b5dfcfb251471dcc39b04c54e25008d617cc34f))
|
||||
* improve full-scaleout of autoscaling event logging ([8435eb3](https://github.com/windmill-labs/windmill/commit/8435eb3adff8429a73db88b12204f7cf8f14d3d2))
|
||||
* improve skip failure on parallel branchall ([a7b2b51](https://github.com/windmill-labs/windmill/commit/a7b2b51444d757964560de3a89024b1c9b0fefe9))
|
||||
|
||||
## [1.483.2](https://github.com/windmill-labs/windmill/compare/v1.483.1...v1.483.2) (2025-04-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* batch reruns query missing workspace_id check in subquery ([#5652](https://github.com/windmill-labs/windmill/issues/5652)) ([444a6ab](https://github.com/windmill-labs/windmill/commit/444a6abad670114c52e44f3606bf6fefc5d3fd98))
|
||||
* **frontend:** fix validity check ([#5654](https://github.com/windmill-labs/windmill/issues/5654)) ([c41c1eb](https://github.com/windmill-labs/windmill/commit/c41c1eb587bf22364f1202310a0c64b6040ab968))
|
||||
* improve MySQL datetime parser timezone handling (WIN-1155) ([#5645](https://github.com/windmill-labs/windmill/issues/5645)) ([5bca8f6](https://github.com/windmill-labs/windmill/commit/5bca8f60e970cc67839edb5dc491685f36cf0499))
|
||||
* track relative imports in python and ts even if lockfile is provided ([e316dbd](https://github.com/windmill-labs/windmill/commit/e316dbd9bdd5c59e9aaba6a4472bb7d832834e84))
|
||||
|
||||
## [1.483.1](https://github.com/windmill-labs/windmill/compare/v1.483.0...v1.483.1) (2025-04-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pin libxml to 0.3.3 ([e5595e4](https://github.com/windmill-labs/windmill/commit/e5595e41b5c87704d814eff95bc00b82195728ba))
|
||||
|
||||
## [1.483.0](https://github.com/windmill-labs/windmill/compare/v1.482.1...v1.483.0) (2025-04-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* handle different aws auth resource type ([#5637](https://github.com/windmill-labs/windmill/issues/5637)) ([5b123b0](https://github.com/windmill-labs/windmill/commit/5b123b01a1318208450789b5bcade447a0b331c7))
|
||||
* oidc support for sqs trigger ([#5614](https://github.com/windmill-labs/windmill/issues/5614)) ([34b307b](https://github.com/windmill-labs/windmill/commit/34b307b2be1f6cf92a81694325f4c333bdd7b055))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix click outside popover fullscreen ([#5631](https://github.com/windmill-labs/windmill/issues/5631)) ([0811457](https://github.com/windmill-labs/windmill/commit/081145726a5d4ab81510a7637126a036823a1565))
|
||||
* improve flow editor step switch performance ([58fa4c8](https://github.com/windmill-labs/windmill/commit/58fa4c80062a5704bbd13ddda1b2f00c7c9e40dd))
|
||||
* linter in early stop doesn't include flow_input ([#5638](https://github.com/windmill-labs/windmill/issues/5638)) ([6a9bdfd](https://github.com/windmill-labs/windmill/commit/6a9bdfd3bd52ff802b6a71c3ae9504bfe7d0421f))
|
||||
* output picker output opening doesn't change id ([#5641](https://github.com/windmill-labs/windmill/issues/5641)) ([64c72b6](https://github.com/windmill-labs/windmill/commit/64c72b6fce669e47f04dc620750840857cbe66cf))
|
||||
|
||||
## [1.482.1](https://github.com/windmill-labs/windmill/compare/v1.482.0...v1.482.1) (2025-04-16)
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ FROM node:20-alpine as frontend
|
||||
# install dependencies
|
||||
WORKDIR /frontend
|
||||
COPY ./frontend/package.json ./frontend/package-lock.json ./
|
||||
COPY ./frontend/scripts/ ./scripts/
|
||||
RUN npm ci
|
||||
|
||||
# Copy all local files into the image.
|
||||
|
||||
@@ -363,6 +363,7 @@ you to have it being synced automatically everyday.
|
||||
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
|
||||
| DISABLE_RESPONSE_LOGS | false | Disable response logs | Server |
|
||||
| CREATE_WORKSPACE_REQUIRE_SUPERADMIN | true | If true, only superadmins can create new workspaces | Server |
|
||||
| MIN_FREE_DISK_SPACE_MB | 15000 | Minimum amount of free space on worker. Sends critical alert if worker has less free space. | Worker |
|
||||
|
||||
## Run a local dev setup
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval \n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ",
|
||||
"query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval\n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -36,5 +36,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45"
|
||||
"hash": "00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -45,56 +45,71 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "subscription_mode: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "gcp_subscription_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"create_update",
|
||||
"existing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -118,6 +133,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -125,5 +141,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f4f6336fc671b00bed7835124892f7a4d3bbe673f7c48153819dab385a5cb357"
|
||||
"hash": "1312b7fd622cc814a406c85dbbff61f003c29185c267642cfd898075ebda855d"
|
||||
}
|
||||
23
backend/.sqlx/query-26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4.json
generated
Normal file
23
backend/.sqlx/query-26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT jsonb_build_object(\n 'kind', jb.kind,\n 'script_path', jb.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),\n 'job_ids', ARRAY_AGG(DISTINCT j.id),\n 'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1]\n ) FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'\n WHERE j.id = ANY(ARRAY_AGG(jb.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM v2_job jb\n WHERE (jb.kind = 'flow' OR jb.kind = 'script')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n GROUP BY jb.kind, jb.runnable_path",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "jsonb_build_object",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,5 +19,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "ea2b88dc050aec038641ea37399d68d4385c5bdc721d1351609f27ca45e4dbdc"
|
||||
"hash": "2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -45,56 +45,71 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "subscription_mode: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "gcp_subscription_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"create_update",
|
||||
"existing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -115,6 +130,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -122,5 +138,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "15fbe481789a7817bf37415fb935f9ed537fd7d3b266d928af4d5d3dd8bb5c18"
|
||||
"hash": "2dcff8b16da75740c362015b2293f578a21813f038d75184a5034c37d4daf36e"
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n j.id,\n j.kind AS \"kind: _\",\n COALESCE(s.path, f.path) AS \"script_path!\",\n COALESCE(s.hash, f.id) AS \"script_hash!: _\",\n COALESCE(jc.started_at, jq.scheduled_for, make_date(1970, 1, 1)) AS \"scheduled_for!: _\",\n args AS input\n FROM v2_job j\n LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script'\n LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow'\n LEFT JOIN v2_job_completed jc ON jc.id = j.id\n LEFT JOIN v2_job_queue jq ON jq.id = j.id\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "kind: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "script_path!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "script_hash!: _",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "scheduled_for!: _",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "input",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3376b42d273c2499f3517c7754f4cdb6eae2e71e56bc8e5215559af967c81bd8"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval \n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker",
|
||||
"query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval\n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,5 +22,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1"
|
||||
"hash": "3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8"
|
||||
}
|
||||
12
backend/.sqlx/query-45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d.json
generated
Normal file
12
backend/.sqlx/query-45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM metrics WHERE created_at < NOW() - INTERVAL '180 day'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d"
|
||||
}
|
||||
@@ -1,80 +1,95 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
|
||||
"query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "aws_auth_resource_type: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "aws_auth_resource_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"oidc",
|
||||
"credentials"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "aws_resource_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"ordinal": 2,
|
||||
"name": "message_attributes",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"ordinal": 3,
|
||||
"name": "queue_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 4,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 5,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -86,6 +101,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
@@ -103,5 +119,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867"
|
||||
"hash": "5237f9d3f57f4e799968a4a9630c984775d23c7a4dc1a7ad37528a46ab89d6bd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2::text::json, $3) RETURNING id",
|
||||
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n VALUES ($1, $2::text::json, $3, $4) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -13,12 +13,13 @@
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Varchar"
|
||||
"Varchar",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2a3992b5e9abcfbb032d10e142d98efa969dae26a7242eb7ac12593ed5421ef3"
|
||||
"hash": "56264b88a9f428e79c6e531a0011e0dbed634f3b12dedaa5fee0362d670887f6"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) \n SELECT worker_ids.worker FROM worker_ids \n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker \n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval",
|
||||
"query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker)\n SELECT worker_ids.worker FROM worker_ids\n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker\n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -18,5 +18,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927"
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT jsonb_build_object(\n 'kind', jb.kind,\n 'script_path', jb.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),\n 'job_ids', ARRAY_AGG(DISTINCT j.id),\n 'schema', ANY_VALUE(COALESCE(s.schema, f.schema))\n ) FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'\n WHERE j.id = ANY(ARRAY_AGG(jb.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM v2_job jb\n WHERE (jb.kind = 'flow' OR jb.kind = 'script')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n GROUP BY jb.kind, jb.runnable_path",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "jsonb_build_object",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5ea1d8c87a17690942ca9e70e0b953e3b32a9a7a73784219d48f8e8932cdb0c8"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by created_at DESC",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,5 +25,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675"
|
||||
"hash": "726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de"
|
||||
}
|
||||
34
backend/.sqlx/query-776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6.json
generated
Normal file
34
backend/.sqlx/query-776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO sqs_trigger (\n aws_auth_resource_type,\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10,\n $11\n )",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
{
|
||||
"Custom": {
|
||||
"name": "aws_auth_resource_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"oidc",
|
||||
"credentials"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"TextArray",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6"
|
||||
}
|
||||
16
backend/.sqlx/query-848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd.json
generated
Normal file
16
backend/.sqlx/query-848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script \n SET ws_error_handler_muted = $3 \n WHERE ctid = (\n SELECT ctid FROM script\n WHERE path = $1 AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1\n )\n",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd"
|
||||
}
|
||||
34
backend/.sqlx/query-8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3.json
generated
Normal file
34
backend/.sqlx/query-8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE \n sqs_trigger \n SET \n aws_auth_resource_type = $1,\n aws_resource_path = $2,\n queue_url = $3,\n message_attributes = $4, \n is_flow = $5, \n edited_by = $6, \n email = $7,\n script_path = $8,\n path = $9,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $10 AND \n path = $11\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
{
|
||||
"Custom": {
|
||||
"name": "aws_auth_resource_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"oidc",
|
||||
"credentials"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"TextArray",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger\n WHERE workspace_id = $1",
|
||||
"query": "\n SELECT \n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n filters AS \"filters: _\",\n initial_messages AS \"initial_messages: _\",\n url_runnable_args AS \"url_runnable_args: _\",\n can_return_message\n FROM \n websocket_trigger\n WHERE \n workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -114,5 +114,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1aa8ead10f8d994f6685d266fcbd409b0fff43111d9600e64b2348401ed8929d"
|
||||
"hash": "8eabeee5527da4aad3f46ad185015262b4304667449f3e1a71efd7477a39f7fc"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
|
||||
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)\n VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)\n ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,5 +17,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe"
|
||||
"hash": "92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO sqs_trigger (\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10\n )",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"TextArray",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8"
|
||||
}
|
||||
@@ -1,80 +1,95 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM sqs_trigger\n WHERE workspace_id = $1",
|
||||
"query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
"name": "aws_auth_resource_type: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "aws_auth_resource_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"oidc",
|
||||
"credentials"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "queue_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "aws_resource_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 2,
|
||||
"name": "message_attributes",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "script_path",
|
||||
"ordinal": 3,
|
||||
"name": "queue_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 4,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 12,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -85,7 +100,6 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -95,6 +109,8 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
@@ -102,5 +118,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7"
|
||||
"hash": "a7df493316f632fc636e4c3c90bef4c98035b5dc808bb8379251c0b35d945ba0"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2::text::json, $3) RETURNING id",
|
||||
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n VALUES ($1, $2::text::json, $3, $4) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -13,12 +13,13 @@
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Varchar"
|
||||
"Varchar",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "83d18f4e1cda2c1867168551d855d5626e766b5469c6976c986bdafc8b9407c5"
|
||||
"hash": "a9f6e5720f748418ae52cf0e31778b9bbef26008803f2dd2a043bdb091b2ff20"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND workspace_id = $2)",
|
||||
"query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,5 +19,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a17260a1f1ee02e786690994d98c84ddf81e2eeb883f895c9cfc47e144d422cb"
|
||||
"hash": "b5860f6a7672a368d740dcd367a8d5ab98fa93e0382a57a698564695db6c40ac"
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n j.kind AS \"kind!: JobKind\",\n j.runnable_id AS \"script_hash: _\",\n j.runnable_path AS script_path,\n COUNT(*) AS \"count!\",\n ANY_VALUE(COALESCE(f.schema, s.schema)) AS schema\n FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND f.path = j.runnable_path AND j.kind = 'flow'\n WHERE COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL\n AND j.workspace_id = $1 AND j.id = ANY($2)\n GROUP BY j.runnable_id, j.runnable_path, j.kind",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "kind!: JobKind",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "script_hash: _",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "schema",
|
||||
"type_info": "Json"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b61a00e6a8ca0a1d24e64fdc9223b077c957da8455ea747b0680923d8282425b"
|
||||
}
|
||||
@@ -1,80 +1,95 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
|
||||
"query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "aws_auth_resource_type: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "aws_auth_resource_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"oidc",
|
||||
"credentials"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "queue_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"ordinal": 2,
|
||||
"name": "aws_resource_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"ordinal": 3,
|
||||
"name": "message_attributes",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 4,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 5,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -83,6 +98,7 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -100,5 +116,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a"
|
||||
"hash": "bc0bedddcafad216c30c5061eef3f4ed8573cda89f655d6490d22f182e3f2f36"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ",
|
||||
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -45,56 +45,71 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "subscription_mode: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "gcp_subscription_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"create_update",
|
||||
"existing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -117,6 +132,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -124,5 +140,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0102308ffa1c0dbfba54d29246535bb81146a4cfae0ec408435570e0813a3bef"
|
||||
"hash": "c2925a28212265bd9ac8e2d498b3588fc040c7a29ae6d9bab1f05af0b535e2ac"
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT DISTINCT ON (j.runnable_path, j.kind) \n j.kind AS \"kind!: JobKind\",\n j.runnable_path AS script_path,\n NULL as \"script_hash: _\",\n -1::bigint as \"count!: _\",\n COALESCE(f.schema, s.schema) AS schema\n FROM v2_job j\n LEFT JOIN script s ON s.path = j.runnable_path AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.path = j.runnable_path AND j.kind = 'flow'\n WHERE COALESCE(s.hash, f.id) IS NOT NULL\n AND j.workspace_id = $1 AND j.id = ANY($2)\n ORDER BY j.runnable_path, j.kind, COALESCE(f.created_at, s.created_at) DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "kind!: JobKind",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "script_hash: _",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "count!: _",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "schema",
|
||||
"type_info": "Json"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c4f382045e5c47986e02f1e57667e6b3cb1d60f15fed9a24e3f05b1bd60c0fc8"
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT j.kind AS \"kind: _\", COALESCE(s.path, f.path) AS \"script_path!\", COALESCE(s.hash, f.id) AS \"script_hash!: _\", args\n FROM v2_job j\n LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script'\n LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow'\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "kind: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "script_path!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "script_hash!: _",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "args",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ccd4c7fe5fbdf1ab4bbbbd7d2a9b1ec3fc844ed286ff563f0c9a5c4778cfb718"
|
||||
}
|
||||
29
backend/.sqlx/query-cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec.json
generated
Normal file
29
backend/.sqlx/query-cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hash",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE \n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update",
|
||||
"query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE\n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -37,5 +37,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5"
|
||||
"hash": "daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE \n sqs_trigger \n SET \n aws_resource_path = $1,\n queue_url = $2,\n message_attributes = $3, \n is_flow = $4, \n edited_by = $5, \n email = $6,\n script_path = $7,\n path = $8,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $9 AND \n path = $10\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"TextArray",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5"
|
||||
}
|
||||
2
backend/.vscode/settings.json
vendored
2
backend/.vscode/settings.json
vendored
@@ -11,5 +11,5 @@
|
||||
"remote.autoForwardPorts": true,
|
||||
"conventionalCommits.scopes": [
|
||||
"restructring triggers, decoding trigger message on work"
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
1897
backend/Cargo.lock
generated
1897
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.482.1"
|
||||
version = "1.486.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -32,7 +32,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.482.1"
|
||||
version = "1.486.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -72,8 +72,9 @@ dind = ["windmill-worker/dind"]
|
||||
websocket = ["windmill-api/websocket"]
|
||||
http_trigger = ["windmill-api/http_trigger"]
|
||||
postgres_trigger = ["windmill-api/postgres_trigger"]
|
||||
mcp = ["windmill-api/mcp"]
|
||||
mqtt_trigger = ["windmill-api/mqtt_trigger"]
|
||||
sqs_trigger = ["windmill-api/sqs_trigger"]
|
||||
sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"]
|
||||
gcp_trigger = ["windmill-api/gcp_trigger"]
|
||||
smtp = ["windmill-api/smtp", "windmill-common/smtp"]
|
||||
license = ["windmill-api/license"]
|
||||
@@ -131,6 +132,8 @@ quote.workspace = true
|
||||
memchr.workspace = true
|
||||
v8 = { workspace = true, optional = true }
|
||||
rustls.workspace = true
|
||||
systemstat.workspace = true
|
||||
size.workspace = true
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = { optional = true, workspace = true }
|
||||
@@ -191,7 +194,7 @@ serde_json = { version = "^1", features = ["preserve_order", "raw_value"] }
|
||||
uuid = { version = "^1", features = ["serde", "v4"] }
|
||||
thiserror = "^2"
|
||||
anyhow = "^1"
|
||||
chrono = { version = "=0.4.39", features = ["serde"] }
|
||||
chrono = { version = "^0.4", features = ["serde"] }
|
||||
chrono-tz = "^0.10.1"
|
||||
tracing = "^0"
|
||||
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
|
||||
@@ -304,6 +307,7 @@ postgres-native-tls = "^0"
|
||||
native-tls = "^0"
|
||||
# samael will break compilation on MacOS. Use this fork instead to make it work
|
||||
# samael = { git="https://github.com/njaremko/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = ["xmlsec"] }
|
||||
libxml = { version = "=0.3.3" }
|
||||
samael = { version="0.0.14", features = ["xmlsec"] }
|
||||
gcp_auth = "0.9.0"
|
||||
rust_decimal = { version = "^1", features = ["db-postgres", "serde-float"]}
|
||||
@@ -313,9 +317,9 @@ nix = { version = "0.27.1", features = ["process", "signal"] }
|
||||
tinyvector = { git = "https://github.com/windmill-labs/tinyvector", rev = "20823b94c20f2b9093f318badd24026cf54dcc85" }
|
||||
hf-hub = "0.3.2"
|
||||
tokenizers = "0.14.1"
|
||||
candle-core = "0.3.0"
|
||||
candle-transformers = "0.3.0"
|
||||
candle-nn = "0.3.0"
|
||||
candle-core = "0.9.1"
|
||||
candle-transformers = "0.9.1"
|
||||
candle-nn = "0.9.1"
|
||||
tiberius = { version = "0.12.3", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]}
|
||||
pin-project = "1"
|
||||
indexmap = { version = "2.2.5", features = ["serde"]}
|
||||
@@ -329,8 +333,8 @@ async-nats = "0.38.0"
|
||||
nkeys = "0.4.4"
|
||||
nu-parser = { version = "0.101.0", default-features = false }
|
||||
|
||||
datafusion = "39.0.0"
|
||||
object_store = { version = "0.10.0", features = ["aws", "azure"] }
|
||||
datafusion = "47.0.0"
|
||||
object_store = { version = "0.12.0", features = ["aws", "azure"] }
|
||||
openidconnect = { version = "4.0.0-rc.1" }
|
||||
aws-config = "^1"
|
||||
aws-sdk-sqs = "1.57.0"
|
||||
@@ -353,9 +357,6 @@ bollard = "0.18.1"
|
||||
tonic = { version = "=0.12.3", features = ["tls-native-roots"] }
|
||||
byteorder = "1.5.0"
|
||||
|
||||
# todo remove
|
||||
half = "=2.4.1"
|
||||
|
||||
tikv-jemallocator = { version = "0.5" }
|
||||
tikv-jemalloc-sys = { version = "^0.5" }
|
||||
tikv-jemalloc-ctl = { version = "^0.5" }
|
||||
@@ -366,6 +367,8 @@ pin-project-lite = "^0"
|
||||
tantivy = "0.22.0"
|
||||
|
||||
backon = "1.3.0"
|
||||
systemstat = "0.2.4"
|
||||
size = "0.5.0"
|
||||
|
||||
flume = { version = "0.11.1", features = ["async"] }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1475a504133766e005ec551004c2af2283663352
|
||||
868ccad87afb804fe22818ecd3d5a091199bcdbf
|
||||
2
backend/migrations/20241223155748_raw_apps_v2.down.sql
Normal file
2
backend/migrations/20241223155748_raw_apps_v2.down.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE app_version DROP COLUMN IF EXISTS raw_app;
|
||||
2
backend/migrations/20241223155748_raw_apps_v2.up.sql
Normal file
2
backend/migrations/20241223155748_raw_apps_v2.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE app_version ADD COLUMN IF NOT EXISTS raw_app BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE sqs_trigger DROP COLUMN aws_auth_resource_type;
|
||||
DROP TYPE IF EXISTS AWS_AUTH_RESOURCE_TYPE;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
CREATE TYPE AWS_AUTH_RESOURCE_TYPE AS ENUM ('oidc', 'credentials');
|
||||
ALTER TABLE sqs_trigger
|
||||
ADD COLUMN aws_auth_resource_type AWS_AUTH_RESOURCE_TYPE DEFAULT 'credentials'::AWS_AUTH_RESOURCE_TYPE NOT NULL;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE gcp_trigger DROP COLUMN subscription_mode;
|
||||
DROP TYPE GCP_SUBSCRIPTION_MODE;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
CREATE TYPE GCP_SUBSCRIPTION_MODE AS ENUM ('create_update', 'existing');
|
||||
ALTER TABLE gcp_trigger ADD COLUMN subscription_mode GCP_SUBSCRIPTION_MODE NOT NULL DEFAULT 'create_update'::GCP_SUBSCRIPTION_MODE;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE websocket_trigger
|
||||
ALTER COLUMN url TYPE VARCHAR(255);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE websocket_trigger
|
||||
ALTER COLUMN url TYPE VARCHAR(1000);
|
||||
@@ -0,0 +1 @@
|
||||
DELETE FROM global_settings WHERE name = 'critical_alerts_on_db_oversize';
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO global_settings (name, value)
|
||||
VALUES ('critical_alerts_on_db_oversize', '{}')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
1
backend/migrations/20250428170426_mcp_mode_log.down.sql
Normal file
1
backend/migrations/20250428170426_mcp_mode_log.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
2
backend/migrations/20250428170426_mcp_mode_log.up.sql
Normal file
2
backend/migrations/20250428170426_mcp_mode_log.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE log_mode ADD VALUE 'mcp';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
CREATE INDEX IF NOT EXISTS idx_metrics_id_created_at ON public.metrics (id, created_at DESC) WHERE id LIKE 'queue_%';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
CREATE INDEX IF NOT EXISTS job_stats_id ON job_stats (job_id);
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
DROP INDEX IF EXISTS index_script_on_path_created_at;
|
||||
CREATE INDEX IF NOT EXISTS index_script_on_path_created_at ON script (workspace_id, path, created_at DESC);
|
||||
@@ -44,6 +44,7 @@ fn replace_full_import(x: &str) -> Option<String> {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"^\#\s?(\S+)\s*$").unwrap();
|
||||
static ref PIN_RE: Regex = Regex::new(r"(?:\s*#\s*(pin|repin):\s*)(\S*)").unwrap();
|
||||
static ref PKG_RE: Regex = Regex::new(r"^([^!=<>]+)(?:[!=<>]|$)").unwrap();
|
||||
}
|
||||
|
||||
fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<NImport> {
|
||||
@@ -167,30 +168,28 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result<Vec<NImport>>
|
||||
.take_while(|e| *e != '\n')
|
||||
.collect::<String>();
|
||||
|
||||
if hs.trim_start().is_empty(){
|
||||
if hs.trim_start().is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
PIN_RE
|
||||
.captures(&hs)
|
||||
.and_then(|x| {
|
||||
x.get(1).zip(x.get(2)).and_then(|(ty_m, pkg_m)| {
|
||||
let pkg = pkg_m.as_str().to_owned();
|
||||
if ty_m.as_str() == "pin" {
|
||||
Some(vec![NImport::Pin {
|
||||
pins: vec![ImportPin { pkg, path: path.to_owned() }],
|
||||
key,
|
||||
}])
|
||||
} else if ty_m.as_str() == "repin" {
|
||||
Some(vec![NImport::Repin {
|
||||
pin: ImportPin { pkg, path: path.to_owned() },
|
||||
key,
|
||||
}])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
PIN_RE.captures(&hs).and_then(|x| {
|
||||
x.get(1).zip(x.get(2)).and_then(|(ty_m, pkg_m)| {
|
||||
let pkg = pkg_m.as_str().to_owned();
|
||||
if ty_m.as_str() == "pin" {
|
||||
Some(vec![NImport::Pin {
|
||||
pins: vec![ImportPin { pkg, path: path.to_owned() }],
|
||||
key,
|
||||
}])
|
||||
} else if ty_m.as_str() == "repin" {
|
||||
Some(vec![NImport::Repin {
|
||||
pin: ImportPin { pkg, path: path.to_owned() },
|
||||
key,
|
||||
}])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
let mut nimports: Vec<NImport> = ast
|
||||
@@ -268,7 +267,14 @@ pub async fn parse_python_imports(
|
||||
Ok(p.pkg)
|
||||
}).collect_vec(),
|
||||
NImportResolved::Repin { pin: ImportPin { pkg, .. }, .. } => vec![Ok(pkg)],
|
||||
NImportResolved::Auto { pkg, ..} => vec![Ok(pkg)],
|
||||
NImportResolved::Auto { pkg, key } => vec![
|
||||
|
||||
if let Some(key) = key {
|
||||
Ok(format!("{pkg} # (mapped from {key})"))
|
||||
} else {
|
||||
Ok(pkg)
|
||||
}
|
||||
],
|
||||
})
|
||||
.flatten()
|
||||
.collect::<error::Result<Vec<String>>>()?
|
||||
@@ -284,6 +290,13 @@ pub async fn parse_python_imports(
|
||||
Ok((imports, compile_error_hint))
|
||||
}
|
||||
|
||||
fn extract_pkg_name(requirement: &str) -> String {
|
||||
PKG_RE
|
||||
.captures(requirement)
|
||||
.map(|x| x.get(1).map(|m| m.as_str().to_string()).unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn parse_python_imports_inner(
|
||||
code: &str,
|
||||
@@ -343,11 +356,15 @@ async fn parse_python_imports_inner(
|
||||
RE.captures(x).and_then(|x| {
|
||||
x.get(1).map(|m| {
|
||||
let requirement = m.as_str().to_string();
|
||||
let key = extract_pkg_name(&requirement);
|
||||
requirements.insert(
|
||||
requirement.clone(),
|
||||
NImportResolved::Repin {
|
||||
pin: ImportPin { pkg: requirement, path: Default::default() },
|
||||
key: Default::default(),
|
||||
key.clone(),
|
||||
NImportResolved::Pin {
|
||||
pins: vec![ImportPin {
|
||||
pkg: requirement.clone(),
|
||||
path: Default::default(),
|
||||
}],
|
||||
key,
|
||||
},
|
||||
);
|
||||
})
|
||||
@@ -368,9 +385,16 @@ async fn parse_python_imports_inner(
|
||||
RE.captures(x).and_then(|x| {
|
||||
x.get(1).map(|m| {
|
||||
let requirement = m.as_str().to_string();
|
||||
let key = extract_pkg_name(&requirement);
|
||||
imports.insert(
|
||||
requirement.clone(),
|
||||
NImportResolved::Auto { key: None, pkg: requirement },
|
||||
key.clone(),
|
||||
NImportResolved::Pin {
|
||||
pins: vec![ImportPin {
|
||||
pkg: requirement,
|
||||
path: Default::default(),
|
||||
}],
|
||||
key,
|
||||
},
|
||||
);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -142,6 +142,8 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
|
||||
"google.cloud.dns" => "google-cloud-dns",
|
||||
"google.cloud.runtimeconfig" => "google-cloud-runtimeconfig",
|
||||
"google.cloud.iot" => "google-cloud-iot",
|
||||
"google.generativeai" => "google-generativeai",
|
||||
"google.genai" => "google-genai",
|
||||
// Azure
|
||||
"azure.mgmt.hybridkubernetes" => "azure-mgmt-hybridkubernetes",
|
||||
"azure.mgmt.sql" => "azure-mgmt-sql",
|
||||
@@ -325,6 +327,7 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
|
||||
"azure.mgmt.nspkg" => "azure-mgmt-nspkg",
|
||||
"azure.keyvault.secrets" => "azure-keyvault-secrets",
|
||||
"azure.storage.blob" => "azure-storage-blob",
|
||||
"azure.storage.filedatalake" => "azure-storage-file-datalake",
|
||||
// Add new entry here ^
|
||||
};
|
||||
|
||||
@@ -376,5 +379,6 @@ pub static SHORT_IMPORTS_MAP: PyMap = phf_map! {
|
||||
"socks" => "PySocks",
|
||||
"taiga" => "python-taiga",
|
||||
"docx" => "python-docx",
|
||||
"vt" => "vt-py",
|
||||
// Add new entry here ^
|
||||
};
|
||||
|
||||
@@ -29,7 +29,15 @@ def main():
|
||||
)
|
||||
.await?;
|
||||
// println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(r, vec!["matplotlib", "wmill", "zanzibar"]);
|
||||
assert_eq!(
|
||||
r,
|
||||
vec![
|
||||
"matplotlib # (mapped from matplotlib.pyplot)",
|
||||
"wmill",
|
||||
"zanzibar # (mapped from zanzibar.estonie)"
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,12 @@ impl Visit for ImportsFinder {
|
||||
pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
|
||||
let cm: Lrc<SourceMap> = Default::default();
|
||||
let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.into());
|
||||
let mut tss = TsSyntax::default();
|
||||
tss.disallow_ambiguous_jsx_like;
|
||||
tss.tsx = true;
|
||||
tss.no_early_errors = true;
|
||||
let lexer = Lexer::new(
|
||||
Syntax::Typescript(TsSyntax::default()),
|
||||
Syntax::Typescript(tss),
|
||||
// EsVersion defaults to es5
|
||||
Default::default(),
|
||||
StringInput::from(&*fm),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde_json::json;
|
||||
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ};
|
||||
@@ -208,15 +210,52 @@ pub struct AnsibleInventory {
|
||||
resource_type: Option<String>,
|
||||
pub pinned_resource: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GitRepo {
|
||||
pub url: String,
|
||||
pub commit: Option<String>,
|
||||
pub branch: Option<String>,
|
||||
pub target_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnsibleRequirements {
|
||||
pub python_reqs: Vec<String>,
|
||||
pub collections: Option<String>,
|
||||
pub roles_and_collections: Option<String>,
|
||||
pub file_resources: Vec<FileResource>,
|
||||
pub inventories: Vec<AnsibleInventory>,
|
||||
pub vars: Vec<(String, String)>,
|
||||
pub resources: Vec<(String, String)>,
|
||||
pub options: AnsiblePlaybookOptions,
|
||||
pub vault_password: Option<String>,
|
||||
pub vault_id: Vec<String>,
|
||||
pub git_repos: Vec<GitRepo>,
|
||||
pub git_ssh_identity: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for AnsibleRequirements {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
python_reqs: vec![],
|
||||
roles_and_collections: None,
|
||||
file_resources: vec![],
|
||||
inventories: vec![],
|
||||
vars: vec![],
|
||||
resources: vec![],
|
||||
options: AnsiblePlaybookOptions {
|
||||
verbosity: None,
|
||||
forks: None,
|
||||
timeout: None,
|
||||
flush_cache: None,
|
||||
force_handlers: None,
|
||||
},
|
||||
vault_password: None,
|
||||
vault_id: vec![],
|
||||
git_repos: vec![],
|
||||
git_ssh_identity: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result<Vec<AnsibleInventory>> {
|
||||
@@ -275,22 +314,7 @@ pub fn parse_ansible_reqs(
|
||||
return Ok((logs, None, inner_content.to_string()));
|
||||
}
|
||||
|
||||
let opts = AnsiblePlaybookOptions {
|
||||
verbosity: None,
|
||||
forks: None,
|
||||
timeout: None,
|
||||
flush_cache: None,
|
||||
force_handlers: None,
|
||||
};
|
||||
let mut ret = AnsibleRequirements {
|
||||
python_reqs: vec![],
|
||||
collections: None,
|
||||
file_resources: vec![],
|
||||
inventories: vec![],
|
||||
vars: vec![],
|
||||
resources: vec![],
|
||||
options: opts,
|
||||
};
|
||||
let mut ret = AnsibleRequirements::default();
|
||||
|
||||
if let Yaml::Hash(doc) = &docs[0] {
|
||||
for (key, value) in doc {
|
||||
@@ -303,7 +327,7 @@ pub fn parse_ansible_reqs(
|
||||
let mut out_str = String::new();
|
||||
let mut emitter = YamlEmitter::new(&mut out_str);
|
||||
emitter.dump(galaxy_requirements)?;
|
||||
ret.collections = Some(out_str);
|
||||
ret.roles_and_collections = Some(out_str);
|
||||
}
|
||||
if let Some(Yaml::Array(py_reqs)) =
|
||||
deps.get(&Yaml::String("python".to_string()))
|
||||
@@ -345,11 +369,60 @@ pub fn parse_ansible_reqs(
|
||||
Yaml::String(key) if key == "inventory" => {
|
||||
ret.inventories = parse_inventories(value)?;
|
||||
}
|
||||
Yaml::String(key) if key == "vault_password" => {
|
||||
let Yaml::String(filename) = value else {
|
||||
return Err(anyhow!(
|
||||
"Vault Password File expects a String containing the file name"
|
||||
));
|
||||
};
|
||||
ret.vault_password = Some(filename.to_string());
|
||||
}
|
||||
Yaml::String(key) if key == "vault_id" => {
|
||||
let Yaml::Array(filenames) = value else {
|
||||
return Err(anyhow!("Vault ID field expects an array of strings in the format: `label@filename`"));
|
||||
};
|
||||
|
||||
for f in filenames {
|
||||
let Yaml::String(filename) = f else {
|
||||
return Err(anyhow!("The elements of the vault_id field should be strings in the format: `label@filename`"));
|
||||
};
|
||||
ret.vault_id.push(filename.to_string());
|
||||
}
|
||||
}
|
||||
Yaml::String(key) if key == "options" => {
|
||||
if let Yaml::Array(opts) = &value {
|
||||
ret.options = parse_ansible_options(opts);
|
||||
}
|
||||
}
|
||||
Yaml::String(key) if key == "git_repos" => {
|
||||
let Yaml::Array(repos) = &value else {
|
||||
return Err(anyhow!("git_repos field expects an array of repos"));
|
||||
};
|
||||
|
||||
for r in repos {
|
||||
ret.git_repos.push(
|
||||
parse_git_repo(r)
|
||||
.map_err(|e| anyhow!("Failed to parse git repo: {e}"))?,
|
||||
);
|
||||
}
|
||||
}
|
||||
Yaml::String(key) if key == "git_ssh_identity" => {
|
||||
let Yaml::Array(indentities) = &value else {
|
||||
return Err(anyhow!(
|
||||
"git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs"
|
||||
));
|
||||
};
|
||||
|
||||
for r in indentities {
|
||||
let Yaml::String(file_name) = r else {
|
||||
return Err(anyhow!(
|
||||
"Git ssh identity file must be a string path to a Windmill variable/secret"
|
||||
));
|
||||
};
|
||||
|
||||
ret.git_ssh_identity.push(file_name.clone());
|
||||
}
|
||||
}
|
||||
Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)),
|
||||
_ => (),
|
||||
}
|
||||
@@ -364,6 +437,38 @@ pub fn parse_ansible_reqs(
|
||||
Ok((logs, Some(ret), out_str))
|
||||
}
|
||||
|
||||
fn parse_git_repo(r: &Yaml) -> anyhow::Result<GitRepo> {
|
||||
let Yaml::Hash(repo) = r else {
|
||||
return Err(anyhow!("Should be a Map"));
|
||||
};
|
||||
|
||||
let url = repo
|
||||
.get(&Yaml::String("url".to_string()))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.ok_or(anyhow!("Expected `url` field"))?;
|
||||
|
||||
let target_path = repo
|
||||
.get(&Yaml::String("target".to_string()))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.ok_or(anyhow!(
|
||||
"Expected `target` field (target directory for cloning the repo)"
|
||||
))?;
|
||||
|
||||
let branch = repo
|
||||
.get(&Yaml::String("branch".to_string()))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let commit = repo
|
||||
.get(&Yaml::String("commit".to_string()))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
Ok(GitRepo { url, commit, branch, target_path })
|
||||
}
|
||||
|
||||
fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
|
||||
let mut ret = AnsiblePlaybookOptions {
|
||||
verbosity: None,
|
||||
@@ -529,3 +634,78 @@ fn yaml_to_json(yaml: &Yaml) -> serde_json::Value {
|
||||
_ => serde_json::Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_versions(
|
||||
section: &str,
|
||||
yaml: &mut Yaml,
|
||||
versions: &HashMap<String, String>,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut logs = String::new();
|
||||
|
||||
let Yaml::Hash(ref mut m) = yaml else {
|
||||
return Err(anyhow!("{section} dependency should be a map"));
|
||||
};
|
||||
|
||||
if let Some(Yaml::Array(elements)) = m.get_mut(&Yaml::String(section.to_string())) {
|
||||
for el in elements {
|
||||
let Yaml::Hash(ref mut h) = el else {
|
||||
return Err(anyhow!("{section} dependency element should be a map"));
|
||||
};
|
||||
|
||||
if let Some(name) = h
|
||||
.get(&Yaml::String("name".to_string()))
|
||||
.and_then(|n| n.as_str())
|
||||
{
|
||||
if let Some(version) = versions.get(name) {
|
||||
h.insert(
|
||||
Yaml::String("version".to_string()),
|
||||
Yaml::String(version.to_string()),
|
||||
);
|
||||
} else {
|
||||
logs.push_str(&format!("WARNING: {section} dependency `{name}` has no locked version, using the latest or system installed version.\n"));
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"{section} dependency element: missing or invalid `name` field"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
pub fn add_versions_to_requirements_yaml(
|
||||
input: &str,
|
||||
role_versions: &HashMap<String, String>,
|
||||
collection_versions: &HashMap<String, String>,
|
||||
) -> anyhow::Result<(String,String)> {
|
||||
let mut docs =
|
||||
YamlLoader::load_from_str(input).map_err(|e| anyhow!("YAML parse error: {}", e))?;
|
||||
let doc = &mut docs[0];
|
||||
|
||||
let mut logs = String::new();
|
||||
|
||||
logs.push_str(
|
||||
&update_versions("roles", doc, role_versions)
|
||||
.map_err(|e| anyhow!("Error updating role versions: {e}"))?,
|
||||
);
|
||||
logs.push_str(
|
||||
&update_versions("collections", doc, collection_versions)
|
||||
.map_err(|e| anyhow!("Error updating collection versions: {e}"))?,
|
||||
);
|
||||
|
||||
if !logs.is_empty() {
|
||||
logs.push_str("WARNING: You might want to try adding manual versions for these, otherwise there could be breaking changes on deployed scripts\n");
|
||||
}
|
||||
|
||||
let mut out_str = String::new();
|
||||
{
|
||||
let mut emitter = YamlEmitter::new(&mut out_str);
|
||||
emitter
|
||||
.dump(doc)
|
||||
.map_err(|e| anyhow!("YAML emit error: {}", e))?;
|
||||
}
|
||||
|
||||
Ok((out_str, logs))
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor::{
|
||||
load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config,
|
||||
load_base_url, load_otel, reload_critical_alerts_on_db_oversize,
|
||||
reload_delete_logs_periodically_setting, reload_indexer_config,
|
||||
reload_instance_python_version_setting, reload_maven_repos_setting,
|
||||
reload_no_default_maven_setting, reload_nuget_config_setting,
|
||||
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
|
||||
send_logs_to_object_store,
|
||||
send_logs_to_object_store, WORKERS_NAMES,
|
||||
};
|
||||
use rand::Rng;
|
||||
use sqlx::postgres::PgListener;
|
||||
@@ -33,15 +34,16 @@ use windmill_common::{
|
||||
agent_workers::build_agent_http_client,
|
||||
get_database_url,
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
|
||||
NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING,
|
||||
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING,
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING,
|
||||
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
|
||||
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING,
|
||||
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING,
|
||||
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING,
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
@@ -265,6 +267,8 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
|
||||
if mode == Mode::Standalone {
|
||||
println!("Running in standalone mode");
|
||||
} else if mode == Mode::MCP {
|
||||
println!("Running in MCP mode");
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
|
||||
@@ -297,7 +301,7 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer {
|
||||
let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer || mode == Mode::MCP {
|
||||
0
|
||||
} else {
|
||||
std::env::var("NUM_WORKERS")
|
||||
@@ -319,8 +323,9 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
&& (mode == Mode::Server || mode == Mode::Standalone);
|
||||
|
||||
let indexer_mode = mode == Mode::Indexer;
|
||||
let mcp_mode = mode == Mode::MCP;
|
||||
|
||||
let server_bind_address: IpAddr = if server_mode || indexer_mode {
|
||||
let server_bind_address: IpAddr = if server_mode || indexer_mode || mcp_mode {
|
||||
std::env::var("SERVER_BIND_ADDR")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
@@ -380,7 +385,7 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
.is_some_and(|x| x == "1" || x == "true");
|
||||
|
||||
if let Some(db) = conn.as_sql() {
|
||||
if !is_agent && !indexer_mode {
|
||||
if !is_agent && !indexer_mode && !mcp_mode {
|
||||
let skip_migration = std::env::var("SKIP_MIGRATION")
|
||||
.map(|val| val == "true")
|
||||
.unwrap_or(false);
|
||||
@@ -443,7 +448,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
if !valid_key && !server_mode {
|
||||
tracing::error!("Invalid license key, workers require a valid license key");
|
||||
}
|
||||
if server_mode {
|
||||
if server_mode || mcp_mode {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
// only force renewal if invalid but not empty (= expired)
|
||||
let renewed_now = maybe_renew_license_key_on_start(
|
||||
@@ -463,10 +468,10 @@ Windmill Community Edition {GIT_VERSION}
|
||||
}
|
||||
}
|
||||
|
||||
if server_mode || worker_mode || indexer_mode {
|
||||
if server_mode || worker_mode || indexer_mode || mcp_mode {
|
||||
let port_var = std::env::var("PORT").ok().and_then(|x| x.parse().ok());
|
||||
|
||||
let port = if server_mode || indexer_mode {
|
||||
let port = if server_mode || indexer_mode || mcp_mode {
|
||||
port_var.unwrap_or(DEFAULT_PORT as u16)
|
||||
} else {
|
||||
port_var.unwrap_or(0)
|
||||
@@ -647,6 +652,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
server_killpill_rx,
|
||||
base_internal_tx,
|
||||
server_mode,
|
||||
mcp_mode,
|
||||
base_internal_url.clone(),
|
||||
)
|
||||
.await?;
|
||||
@@ -913,6 +919,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::error!(error = %e, "Could not reload critical error emails setting");
|
||||
}
|
||||
},
|
||||
CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING => {
|
||||
if let Err(e) = reload_critical_alerts_on_db_oversize(&db).await {
|
||||
tracing::error!(error = %e, "Could not reload critical alerts on db oversize setting");
|
||||
}
|
||||
|
||||
},
|
||||
JWT_SECRET_SETTING => {
|
||||
if let Err(e) = reload_jwt_secret_setting(&db).await {
|
||||
tracing::error!(error = %e, "Could not reload jwt secret setting");
|
||||
@@ -1049,15 +1061,19 @@ Windmill Community Edition {GIT_VERSION}
|
||||
}
|
||||
}
|
||||
|
||||
futures::try_join!(
|
||||
shutdown_signal,
|
||||
workers_f,
|
||||
monitor_f,
|
||||
server_f,
|
||||
metrics_f,
|
||||
indexer_f,
|
||||
log_indexer_f
|
||||
)?;
|
||||
if mcp_mode {
|
||||
futures::try_join!(shutdown_signal, workers_f, server_f)?;
|
||||
} else {
|
||||
futures::try_join!(
|
||||
shutdown_signal,
|
||||
workers_f,
|
||||
monitor_f,
|
||||
server_f,
|
||||
metrics_f,
|
||||
indexer_f,
|
||||
log_indexer_f
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
tracing::info!("Nothing to do, exiting.");
|
||||
}
|
||||
@@ -1206,10 +1222,12 @@ pub async fn run_workers(
|
||||
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
|
||||
*windmill_worker::SLEEP_QUEUE
|
||||
);
|
||||
|
||||
for i in 1..(num_workers + 1) {
|
||||
let wk_conf = &workers[i as usize - 1];
|
||||
let conn1 = wk_conf.conn.clone();
|
||||
let worker_name = wk_conf.worker_name.clone();
|
||||
WORKERS_NAMES.write().await.push(worker_name.clone());
|
||||
let ip = ip.clone();
|
||||
let rx = killpill_rxs.pop().unwrap();
|
||||
let tx = tx.clone();
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tokio::{
|
||||
join,
|
||||
@@ -30,28 +30,54 @@ use windmill_api::{
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee::low_disk_alerts;
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
use windmill_common::global_settings::OAUTH_SETTING;
|
||||
use windmill_common::{
|
||||
utils::empty_string_as_none,
|
||||
agent_workers::DECODED_AGENT_TOKEN, auth::create_token_for_owner, ee::CriticalErrorChannel, error, flow_status::{FlowStatus, FlowStatusModule}, global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
NUGET_CONFIG_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
agent_workers::DECODED_AGENT_TOKEN,
|
||||
auth::create_token_for_owner,
|
||||
ee::CriticalErrorChannel,
|
||||
error,
|
||||
flow_status::{FlowStatus, FlowStatusModule},
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING,
|
||||
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
|
||||
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
}, indexer::load_indexer_config, jobs::QueuedJob, jwt::JWT_SECRET, oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH, server::load_smtp_config, tracing_init::JSON_FMT, users::truncate_token, utils::{now_from_db, rd_string, report_critical_error, Mode}, worker::{
|
||||
load_worker_config, reload_custom_tags_setting, store_pull_query, store_suspended_pull_query, update_min_version, Connection, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR, WORKER_CONFIG, WORKER_GROUP
|
||||
}, KillpillSender, BASE_URL, CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED, MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
|
||||
},
|
||||
indexer::load_indexer_config,
|
||||
jobs::QueuedJob,
|
||||
jwt::JWT_SECRET,
|
||||
oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH,
|
||||
server::load_smtp_config,
|
||||
tracing_init::JSON_FMT,
|
||||
users::truncate_token,
|
||||
utils::empty_string_as_none,
|
||||
utils::{now_from_db, rd_string, report_critical_error, Mode},
|
||||
worker::{
|
||||
load_worker_config, reload_custom_tags_setting, store_pull_query,
|
||||
store_suspended_pull_query, update_min_version, Connection, DEFAULT_TAGS_PER_WORKSPACE,
|
||||
DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR,
|
||||
WORKER_CONFIG, WORKER_GROUP,
|
||||
},
|
||||
KillpillSender, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE, CRITICAL_ALERT_MUTE_UI_ENABLED,
|
||||
CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
|
||||
METRICS_DEBUG_ENABLED, METRICS_ENABLED, MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED,
|
||||
OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
|
||||
};
|
||||
use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload};
|
||||
use windmill_worker::{
|
||||
handle_job_error, AuthedClient, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL
|
||||
handle_job_error, AuthedClient, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
|
||||
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
|
||||
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -111,7 +137,7 @@ lazy_static::lazy_static! {
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
|
||||
pub static ref WORKERS_NAMES: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
|
||||
|
||||
static ref QUEUE_COUNT_TAGS: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
|
||||
static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true");
|
||||
@@ -135,7 +161,6 @@ pub async fn initial_load(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let Err(e) = load_metrics_enabled(conn).await {
|
||||
tracing::error!("Error loading expose metrics: {e:#}");
|
||||
}
|
||||
@@ -161,6 +186,12 @@ pub async fn initial_load(
|
||||
if server_mode {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
load_require_preexisting_user(db).await;
|
||||
if let Err(e) = reload_critical_alerts_on_db_oversize(db).await {
|
||||
tracing::error!(
|
||||
"Error reloading critical alerts on db oversize setting: {:?}",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,11 +203,13 @@ pub async fn initial_load(
|
||||
}
|
||||
Connection::Http(_) => {
|
||||
// TODO: reload worker config from http
|
||||
WORKER_CONFIG.write().await.worker_tags = DECODED_AGENT_TOKEN.as_ref().map(|x| x.tags.clone()).unwrap_or_default();
|
||||
WORKER_CONFIG.write().await.worker_tags = DECODED_AGENT_TOKEN
|
||||
.as_ref()
|
||||
.map(|x| x.tags.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let Err(e) = reload_hub_base_url_setting(conn, server_mode).await {
|
||||
tracing::error!("Error reloading hub base url: {:?}", e)
|
||||
@@ -190,7 +223,6 @@ pub async fn initial_load(
|
||||
if let Err(e) = reload_custom_tags_setting(db).await {
|
||||
tracing::error!("Error reloading custom tags: {:?}", e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -225,7 +257,8 @@ pub async fn initial_load(
|
||||
}
|
||||
|
||||
pub async fn load_metrics_enabled(conn: &Connection) -> error::Result<()> {
|
||||
let metrics_enabled = load_value_from_global_settings_with_conn(conn, EXPOSE_METRICS_SETTING, true).await;
|
||||
let metrics_enabled =
|
||||
load_value_from_global_settings_with_conn(conn, EXPOSE_METRICS_SETTING, true).await;
|
||||
match metrics_enabled {
|
||||
Ok(Some(serde_json::Value::Bool(t))) => METRICS_ENABLED.store(t, Ordering::Relaxed),
|
||||
_ => (),
|
||||
@@ -345,13 +378,13 @@ pub async fn reload_critical_alert_mute_ui_setting(conn: &Connection) -> error::
|
||||
load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true).await
|
||||
{
|
||||
CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed);
|
||||
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_metrics_debug_enabled(conn: &Connection) -> error::Result<()> {
|
||||
let metrics_enabled = load_value_from_global_settings_with_conn(conn, EXPOSE_DEBUG_METRICS_SETTING, true).await;
|
||||
let metrics_enabled =
|
||||
load_value_from_global_settings_with_conn(conn, EXPOSE_DEBUG_METRICS_SETTING, true).await;
|
||||
match metrics_enabled {
|
||||
Ok(Some(serde_json::Value::Bool(t))) => {
|
||||
METRICS_DEBUG_ENABLED.store(t, Ordering::Relaxed);
|
||||
@@ -575,7 +608,9 @@ async fn send_log_file_to_object_store(
|
||||
};
|
||||
|
||||
let exists = LAST_LOG_FILE_SENT.lock().map(|last_log_file_sent| {
|
||||
last_log_file_sent.map(|last_log_file_sent| last_log_file_sent >= ts).unwrap_or(false)
|
||||
last_log_file_sent
|
||||
.map(|last_log_file_sent| last_log_file_sent >= ts)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
if exists.unwrap_or(false) {
|
||||
@@ -612,7 +647,9 @@ async fn send_log_file_to_object_store(
|
||||
let (ok_lines, err_lines) = read_log_counters(ts_str);
|
||||
|
||||
if let Some(db) = conn.as_sql() {
|
||||
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
|
||||
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)
|
||||
VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7",
|
||||
hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT)
|
||||
.execute(db)
|
||||
.await {
|
||||
@@ -1002,11 +1039,21 @@ pub async fn reload_nuget_config_setting(conn: &Connection) {
|
||||
.await;
|
||||
}
|
||||
pub async fn reload_maven_repos_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(conn, windmill_common::global_settings::MAVEN_REPOS_SETTING, "MAVEN_REPOS", MAVEN_REPOS.clone())
|
||||
.await;
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
windmill_common::global_settings::MAVEN_REPOS_SETTING,
|
||||
"MAVEN_REPOS",
|
||||
MAVEN_REPOS.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
pub async fn reload_no_default_maven_setting(conn: &Connection) {
|
||||
let value = load_value_from_global_settings_with_conn(conn, windmill_common::global_settings::NO_DEFAULT_MAVEN_SETTING, true).await;
|
||||
let value = load_value_from_global_settings_with_conn(
|
||||
conn,
|
||||
windmill_common::global_settings::NO_DEFAULT_MAVEN_SETTING,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
match value {
|
||||
Ok(Some(serde_json::Value::Bool(t))) => NO_DEFAULT_MAVEN.store(t, Ordering::Relaxed),
|
||||
Err(e) => {
|
||||
@@ -1175,7 +1222,6 @@ pub async fn load_value_from_global_settings(
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
|
||||
pub async fn load_value_from_global_settings_with_conn(
|
||||
conn: &Connection,
|
||||
setting_name: &str,
|
||||
@@ -1185,14 +1231,18 @@ pub async fn load_value_from_global_settings_with_conn(
|
||||
Connection::Sql(db) => Ok(load_value_from_global_settings(db, setting_name).await?),
|
||||
Connection::Http(client) => {
|
||||
if load_from_http {
|
||||
client.get::<Option<serde_json::Value>>(&format!("/api/agent_workers/get_global_setting/{}", setting_name)).await
|
||||
.map_err(|e| anyhow::anyhow!("Error loading setting {}: {}", setting_name, e))
|
||||
client
|
||||
.get::<Option<serde_json::Value>>(&format!(
|
||||
"/api/agent_workers/get_global_setting/{}",
|
||||
setting_name
|
||||
))
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Error loading setting {}: {}", setting_name, e))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
|
||||
@@ -1314,12 +1364,12 @@ pub async fn monitor_db(
|
||||
let zombie_jobs_f = async {
|
||||
if server_mode && !initial_load && !*DISABLE_ZOMBIE_JOBS_MONITORING {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
handle_zombie_jobs(db, base_internal_url, "server").await;
|
||||
match handle_zombie_flows(db).await {
|
||||
Err(err) => {
|
||||
tracing::error!("Error handling zombie flows: {:?}", err);
|
||||
},
|
||||
_ => {}
|
||||
handle_zombie_jobs(db, base_internal_url, "server").await;
|
||||
match handle_zombie_flows(db).await {
|
||||
Err(err) => {
|
||||
tracing::error!("Error handling zombie flows: {:?}", err);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1327,7 +1377,7 @@ pub async fn monitor_db(
|
||||
let expired_items_f = async {
|
||||
if server_mode && !initial_load {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
delete_expired_items(&db).await;
|
||||
delete_expired_items(&db).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1365,6 +1415,23 @@ pub async fn monitor_db(
|
||||
}
|
||||
};
|
||||
|
||||
let low_disk_alerts_f = async {
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(db) = conn.as_sql() {
|
||||
low_disk_alerts(
|
||||
&db,
|
||||
server_mode,
|
||||
_worker_mode,
|
||||
WORKERS_NAMES.read().await.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
{
|
||||
()
|
||||
}
|
||||
};
|
||||
|
||||
let apply_autoscaling_f = async {
|
||||
#[cfg(feature = "enterprise")]
|
||||
if server_mode && !initial_load {
|
||||
@@ -1387,6 +1454,7 @@ pub async fn monitor_db(
|
||||
verify_license_key_f,
|
||||
worker_groups_alerts_f,
|
||||
jobs_waiting_alerts_f,
|
||||
low_disk_alerts_f,
|
||||
apply_autoscaling_f,
|
||||
update_min_worker_version_f,
|
||||
);
|
||||
@@ -1496,11 +1564,7 @@ pub async fn reload_indexer_config(db: &Pool<Postgres>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reload_worker_config(
|
||||
db: &DB,
|
||||
tx: KillpillSender,
|
||||
kill_if_change: bool,
|
||||
) {
|
||||
pub async fn reload_worker_config(db: &DB, tx: KillpillSender, kill_if_change: bool) {
|
||||
let config = load_worker_config(db, tx.clone()).await;
|
||||
if let Err(e) = config {
|
||||
tracing::error!("Error reloading worker config: {:?}", e)
|
||||
@@ -1543,7 +1607,8 @@ pub async fn reload_worker_config(
|
||||
}
|
||||
|
||||
pub async fn load_base_url(conn: &Connection) -> error::Result<String> {
|
||||
let q_base_url = load_value_from_global_settings_with_conn(conn, BASE_URL_SETTING, false).await?;
|
||||
let q_base_url =
|
||||
load_value_from_global_settings_with_conn(conn, BASE_URL_SETTING, false).await?;
|
||||
|
||||
let std_base_url = std::env::var("BASE_URL")
|
||||
.ok()
|
||||
@@ -1574,10 +1639,9 @@ pub async fn load_base_url(conn: &Connection) -> error::Result<String> {
|
||||
}
|
||||
|
||||
pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
let oauths = if let Some(db) = conn.as_sql() {
|
||||
let q_oauth = load_value_from_global_settings (db, OAUTH_SETTING).await?;
|
||||
let q_oauth = load_value_from_global_settings(db, OAUTH_SETTING).await?;
|
||||
|
||||
if let Some(q) = q_oauth {
|
||||
if let Ok(v) = serde_json::from_value::<
|
||||
@@ -1652,7 +1716,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
increment_counter AS (
|
||||
INSERT INTO zombie_job_counter (job_id, counter)
|
||||
SELECT id, 1 FROM to_update WHERE counter < $2
|
||||
ON CONFLICT (job_id) DO UPDATE
|
||||
ON CONFLICT (job_id) DO UPDATE
|
||||
SET counter = zombie_job_counter.counter + 1
|
||||
),
|
||||
update_concurrency AS (
|
||||
@@ -1744,7 +1808,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
|
||||
let same_worker_timeout_jobs = {
|
||||
let long_same_worker_jobs = sqlx::query!(
|
||||
"SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval
|
||||
"SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval
|
||||
AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker",
|
||||
)
|
||||
.fetch_all(db)
|
||||
@@ -1758,9 +1822,9 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let long_dead_workers: std::collections::HashSet<String> = sqlx::query_scalar!(
|
||||
"WITH worker_ids AS (SELECT unnest($1::text[]) as worker)
|
||||
SELECT worker_ids.worker FROM worker_ids
|
||||
LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker
|
||||
"WITH worker_ids AS (SELECT unnest($1::text[]) as worker)
|
||||
SELECT worker_ids.worker FROM worker_ids
|
||||
LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker
|
||||
WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval",
|
||||
&worker_ids[..]
|
||||
)
|
||||
@@ -1804,7 +1868,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
let non_restartable_jobs = if *RESTART_ZOMBIE_JOBS {
|
||||
vec![]
|
||||
} else {
|
||||
sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval
|
||||
sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval
|
||||
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false")
|
||||
.bind(ZOMBIE_JOB_TIMEOUT.as_str())
|
||||
.fetch_all(db)
|
||||
@@ -1863,7 +1927,8 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
|
||||
mpsc::channel::<SameWorkerPayload>(1);
|
||||
let same_worker_tx_never_used =
|
||||
SameWorkerSender(same_worker_tx_never_used, Arc::new(AtomicU16::new(0)));
|
||||
let (send_result_never_used, _send_result_rx_never_used) = JobCompletedSender::new_never_used();
|
||||
let (send_result_never_used, _send_result_rx_never_used) =
|
||||
JobCompletedSender::new_never_used();
|
||||
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by)
|
||||
&& job.permissioned_as != job.created_by
|
||||
@@ -2001,7 +2066,7 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> {
|
||||
}
|
||||
);
|
||||
report_critical_error(reason.clone(), db.clone(), Some(&flow.workspace_id), None).await;
|
||||
cancel_zombie_flow_job(db, flow.id, &flow.workspace_id,
|
||||
cancel_zombie_flow_job(db, flow.id, &flow.workspace_id,
|
||||
format!(r#"{reason}
|
||||
This would happen if a worker was interrupted, killed or crashed while doing a state transition at the end of a job which is always an unexpected behavior that should never happen.
|
||||
Please check your worker logs for more details and feel free to report it to the Windmill team on our Discord or support@windmill.dev (response for non EE customers will be best effort) with as much context as possible, ideally:
|
||||
@@ -2018,7 +2083,7 @@ Please check your worker logs for more details and feel free to report it to the
|
||||
r#"
|
||||
DELETE
|
||||
FROM parallel_monitor_lock
|
||||
WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval
|
||||
WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval
|
||||
RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q
|
||||
WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL
|
||||
) AS workspace_id
|
||||
@@ -2073,8 +2138,12 @@ async fn cancel_zombie_flow_job(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reload_hub_base_url_setting(conn: &Connection, server_mode: bool) -> error::Result<()> {
|
||||
let hub_base_url = load_value_from_global_settings_with_conn(conn, HUB_BASE_URL_SETTING, true).await?;
|
||||
pub async fn reload_hub_base_url_setting(
|
||||
conn: &Connection,
|
||||
server_mode: bool,
|
||||
) -> error::Result<()> {
|
||||
let hub_base_url =
|
||||
load_value_from_global_settings_with_conn(conn, HUB_BASE_URL_SETTING, true).await?;
|
||||
|
||||
let base_url = if let Some(q) = hub_base_url {
|
||||
if let Ok(v) = serde_json::from_value::<String>(q.clone()) {
|
||||
@@ -2126,7 +2195,7 @@ pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result<
|
||||
v
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Could not parse critical_error_emails setting as an array of channels, found: {:#?}",
|
||||
"Could not parse critical_error_channels setting as an array of channels, found: {:#?}",
|
||||
&q
|
||||
);
|
||||
vec![]
|
||||
@@ -2141,6 +2210,37 @@ pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct DBOversize {
|
||||
enabled: bool,
|
||||
value: f32,
|
||||
}
|
||||
let db_oversize_value =
|
||||
load_value_from_global_settings(conn, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING).await?;
|
||||
|
||||
let db_oversize = if let Some(q) = db_oversize_value {
|
||||
match serde_json::from_value::<DBOversize>(q.clone()) {
|
||||
Ok(DBOversize { enabled: true, value }) => Some(value),
|
||||
Ok(_) => None,
|
||||
Err(q) => {
|
||||
tracing::error!(
|
||||
"Could not parse critical_alerts_on_db_oversize setting, found: {:#?}",
|
||||
&q
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut l = CRITICAL_ALERTS_ON_DB_OVERSIZE.write().await;
|
||||
*l = db_oversize;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn generate_and_save_jwt_secret(db: &DB) -> error::Result<String> {
|
||||
let secret = rd_string(32);
|
||||
sqlx::query!(
|
||||
|
||||
@@ -16,16 +16,20 @@ use tokio::sync::RwLock;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use windmill_api_client::types::{CreateFlowBody, RawScript};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs};
|
||||
use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage};
|
||||
|
||||
use serde::Serialize;
|
||||
#[cfg(feature = "deno_core")]
|
||||
use windmill_common::flows::InputTransform;
|
||||
use windmill_common::worker::WORKER_CONFIG;
|
||||
|
||||
use windmill_common::{
|
||||
flow_status::{FlowStatus, FlowStatusModule, RestartedFrom},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue},
|
||||
jobs::{JobKind, JobPayload, RawCode},
|
||||
jwt::JWT_SECRET,
|
||||
scripts::{ScriptHash, ScriptLang},
|
||||
@@ -137,10 +141,11 @@ impl ApiServer {
|
||||
rx,
|
||||
port_tx,
|
||||
false,
|
||||
false,
|
||||
format!("http://localhost:{}", addr.port()),
|
||||
));
|
||||
|
||||
_port_rx.await.unwrap();
|
||||
_port_rx.await.expect("failed to receive port");
|
||||
|
||||
// clear the cache between tests
|
||||
windmill_common::cache::clear();
|
||||
@@ -166,6 +171,7 @@ impl ApiServer {
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
fn get_module(cjob: &CompletedJob, id: &str) -> Option<FlowStatusModule> {
|
||||
cjob.flow_status.clone().and_then(|fs| {
|
||||
find_module_in_vec(
|
||||
@@ -175,6 +181,7 @@ fn get_module(cjob: &CompletedJob, id: &str) -> Option<FlowStatusModule> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
fn find_module_in_vec(modules: Vec<FlowStatusModule>, id: &str) -> Option<FlowStatusModule> {
|
||||
modules.into_iter().find(|s| s.id() == id)
|
||||
}
|
||||
@@ -283,6 +290,7 @@ mod suspend_resume {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -365,6 +373,7 @@ mod suspend_resume {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn cancel_from_job(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -390,6 +399,7 @@ mod suspend_resume {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn cancel_after_suspend(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -563,6 +573,7 @@ def main(last, port):
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_pass(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -608,6 +619,7 @@ def main(last, port):
|
||||
assert_eq!(json!([3, 5, 7, 9]), result);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_fail_step_zero(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -651,6 +663,7 @@ def main(last, port):
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_fail_step_one(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -692,6 +705,7 @@ def main(last, port):
|
||||
.contains("index out of range"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_with_failure_module(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -768,6 +782,7 @@ def main(error, port):
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_iteration(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -826,6 +841,7 @@ async fn test_iteration(db: Pool<Postgres>) {
|
||||
.contains("2"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_iteration_parallel(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1104,6 +1120,7 @@ trait StreamFind: futures::Stream + Unpin + Sized {
|
||||
|
||||
impl<T: futures::Stream + Unpin + Sized> StreamFind for T {}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1222,6 +1239,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_identity(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1259,6 +1277,7 @@ async fn test_identity(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!(42));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1534,6 +1553,7 @@ async fn test_flow_result_by_id(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!([[42]]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_stop_after_if(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1587,6 +1607,7 @@ async fn test_stop_after_if(db: Pool<Postgres>) {
|
||||
assert_eq!(json!(-123), result);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_stop_after_if_nested(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1645,6 +1666,7 @@ async fn test_stop_after_if_nested(db: Pool<Postgres>) {
|
||||
assert_eq!(json!([-123]), result);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "deno_core", feature = "python"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_flow(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1702,6 +1724,7 @@ async fn test_python_flow(db: Pool<Postgres>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_flow_2(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -1776,6 +1799,7 @@ func main(derp string) (string, error) {
|
||||
assert_eq!(result, serde_json::json!("hello world"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_rust_job(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2031,6 +2055,7 @@ public class Main {
|
||||
assert_eq!(job.json_result(), Some(json!("hello world")));
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_job(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2064,6 +2089,7 @@ def main():
|
||||
assert_eq!(result, serde_json::json!("hello world"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_job_heavy_dep(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2100,6 +2126,7 @@ def main():
|
||||
assert_eq!(result, serde_json::json!(3));
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_job_with_imports(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2203,6 +2230,7 @@ export async function main(a: Date) {
|
||||
assert_eq!(result, serde_json::json!("object"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_job_datetime_and_bytes(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2238,6 +2266,7 @@ def main(a: datetime, b: bytes):
|
||||
assert_eq!(result, serde_json::json!([true, true]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_empty_loop_1(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2294,6 +2323,7 @@ async fn test_empty_loop_1(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!(0));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_invalid_first_step(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2374,6 +2404,7 @@ async fn test_empty_loop_2(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!([]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_step_after_loop(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2497,6 +2528,7 @@ async fn test_branchone_simple(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!([1, 2]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_branchone_with_cond(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2533,6 +2565,7 @@ async fn test_branchone_with_cond(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!([1, 3]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_branchall_sequential(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2571,6 +2604,7 @@ async fn test_branchall_sequential(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!([[1, 2], [1, 3]]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_branchall_simple(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2698,6 +2732,7 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_branchone_nested(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2817,6 +2852,7 @@ async fn test_branchall_nested(db: Pool<Postgres>) {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_failure_module(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2929,6 +2965,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
assert_eq!(json!({ "l": [0, 1, 2] }), result);
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_flow_lock_all(db: Pool<Postgres>) {
|
||||
use futures::StreamExt;
|
||||
@@ -3067,6 +3104,7 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
|
||||
async fn test_complex_flow_restart(db: Pool<Postgres>) {
|
||||
@@ -3751,6 +3789,7 @@ export async function main() {
|
||||
run_preview_relative_imports(&db, content, ScriptLang::Bun).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "relative_bun"))]
|
||||
async fn test_nested_imports_bun(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
@@ -3799,6 +3838,7 @@ export async function main() {
|
||||
run_preview_relative_imports(&db, content, ScriptLang::Deno).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "relative_python"))]
|
||||
async fn test_relative_imports_python(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
@@ -3816,6 +3856,7 @@ def main():
|
||||
run_preview_relative_imports(&db, content, ScriptLang::Python3).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "relative_python"))]
|
||||
async fn test_nested_imports_python(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
@@ -3831,6 +3872,7 @@ def main():
|
||||
run_preview_relative_imports(&db, content, ScriptLang::Python3).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
async fn assert_lockfile(
|
||||
db: &Pool<Postgres>,
|
||||
script_content: String,
|
||||
@@ -3923,6 +3965,8 @@ async fn assert_lockfile(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_requirements_python(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
@@ -3948,6 +3992,8 @@ def main():
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_extra_requirements_python(db: Pool<Postgres>) {
|
||||
{
|
||||
@@ -3975,9 +4021,10 @@ def main():
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_extra_requirements_python2(db: Pool<Postgres>) {
|
||||
|
||||
let content = r#"
|
||||
# py311
|
||||
# extra_requirements:
|
||||
@@ -3993,26 +4040,23 @@ def main():
|
||||
&db,
|
||||
content,
|
||||
ScriptLang::Python3,
|
||||
vec![
|
||||
"# py311",
|
||||
"simplejson==3.20.1",
|
||||
"tiny==0.1.3"
|
||||
],
|
||||
vec!["# py311", "simplejson==3.20.1", "tiny==0.1.3"],
|
||||
)
|
||||
.await;
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_pins_python(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
# py311
|
||||
# extra_requirements:
|
||||
# tiny==0.1.3
|
||||
# bottle==0.13.2
|
||||
|
||||
import f.system.requirements
|
||||
import f.system.pins
|
||||
import tiny # repin: bottle==0.13.0
|
||||
import tiny # repin: tiny==0.1.3
|
||||
import simplejson
|
||||
|
||||
def main():
|
||||
@@ -4026,10 +4070,10 @@ def main():
|
||||
ScriptLang::Python3,
|
||||
vec![
|
||||
"# py311",
|
||||
"bottle==0.13.0",
|
||||
"bottle==0.13.2",
|
||||
"microdot==2.2.0",
|
||||
"simplejson==3.19.3",
|
||||
"tiny==0.1.3"
|
||||
"tiny==0.1.3",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -4285,6 +4329,7 @@ mod job_payload {
|
||||
];
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_script_hash_payload(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -4445,6 +4490,7 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_flow_node_payload(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -4629,6 +4675,7 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_flow_payload(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -4671,6 +4718,7 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_flow_payload_with_preprocessor(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -4738,6 +4786,7 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_restarted_flow_payload(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -4790,6 +4839,7 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_raw_flow_payload(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -4836,6 +4886,7 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_raw_flow_payload_with_restarted_from(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -13,9 +13,9 @@ default = []
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker/enterprise"]
|
||||
stripe = []
|
||||
agent_worker_server = []
|
||||
enterprise_saml = ["dep:samael"]
|
||||
enterprise_saml = ["dep:samael", "dep:libxml"]
|
||||
benchmark = []
|
||||
embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn", "dep:half"]
|
||||
embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn"]
|
||||
parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet", "windmill-worker/parquet"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker/prometheus"]
|
||||
openidconnect = ["dep:openidconnect"]
|
||||
@@ -35,8 +35,10 @@ sqs_trigger = ["dep:aws-sdk-sqs", "dep:thiserror", "dep:aws-config"]
|
||||
deno_core = ["dep:deno_core", "dep:deno_error"]
|
||||
gcp_trigger = ["dep:thiserror", "dep:google-cloud-pubsub", "dep:google-cloud-googleapis", "dep:tonic"]
|
||||
cloud = ["windmill-common/cloud"]
|
||||
mcp = ["dep:rmcp"]
|
||||
|
||||
[dependencies]
|
||||
rmcp = { git = "https://github.com/windmill-labs/rust-sdk", features = ["transport-sse-server"], optional = true }
|
||||
windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-audit.workspace = true
|
||||
@@ -52,7 +54,6 @@ tokio-stream.workspace = true
|
||||
anyhow.workspace = true
|
||||
argon2.workspace = true
|
||||
axum.workspace = true
|
||||
half = { workspace = true, optional = true}
|
||||
futures.workspace = true
|
||||
git-version.workspace = true
|
||||
tower.workspace = true
|
||||
@@ -100,6 +101,7 @@ async_zip = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
bytes.workspace = true
|
||||
samael = { workspace = true, optional = true }
|
||||
libxml = { workspace = true, optional = true }
|
||||
async-recursion.workspace = true
|
||||
rsa = { workspace = true, optional = true}
|
||||
uuid.workspace = true
|
||||
@@ -133,6 +135,7 @@ rust-postgres-native-tls = { workspace = true, optional = true}
|
||||
rumqttc = { workspace = true, optional = true }
|
||||
aws-sdk-sqs = { workspace = true, optional = true }
|
||||
aws-config = { workspace = true, optional = true }
|
||||
aws-sdk-sts = { workspace = true, optional = true }
|
||||
google-cloud-pubsub = { workspace = true, optional = true }
|
||||
google-cloud-googleapis = { workspace = true , optional = true }
|
||||
tonic = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.482.1
|
||||
version: 1.486.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -5985,6 +5985,55 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/create_raw:
|
||||
post:
|
||||
summary: create app raw
|
||||
operationId: createAppRaw
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: new app
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
app:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
value: {}
|
||||
summary:
|
||||
type: string
|
||||
policy:
|
||||
$ref: "#/components/schemas/Policy"
|
||||
draft_only:
|
||||
type: boolean
|
||||
deployment_message:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
- summary
|
||||
- policy
|
||||
js:
|
||||
type: string
|
||||
css:
|
||||
type: string
|
||||
responses:
|
||||
"201":
|
||||
description: app created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/exists/{path}:
|
||||
get:
|
||||
summary: does an app exisst at path
|
||||
@@ -6344,6 +6393,49 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/update_raw/{path}:
|
||||
post:
|
||||
summary: update app
|
||||
operationId: updateAppRaw
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
requestBody:
|
||||
description: update app
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
app:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
summary:
|
||||
type: string
|
||||
value: {}
|
||||
policy:
|
||||
$ref: "#/components/schemas/Policy"
|
||||
deployment_message:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
js:
|
||||
type: string
|
||||
css:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: app updated
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/custom_path_exists/{custom_path}:
|
||||
get:
|
||||
summary: check if custom path exists
|
||||
@@ -9447,7 +9539,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewGcpTrigger"
|
||||
$ref: "#/components/schemas/GcpTriggerData"
|
||||
responses:
|
||||
"201":
|
||||
description: gcp trigger created
|
||||
@@ -9471,7 +9563,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/EditGcpTrigger"
|
||||
$ref: "#/components/schemas/GcpTriggerData"
|
||||
responses:
|
||||
"200":
|
||||
description: gcp trigger updated
|
||||
@@ -9665,7 +9757,7 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/gcp_triggers/subscriptions/list/{path}:
|
||||
post:
|
||||
summary: list all subscription of a give topic from google cloud service
|
||||
@@ -14040,7 +14132,7 @@ components:
|
||||
csharp,
|
||||
nu,
|
||||
java
|
||||
# for related places search: ADD_NEW_LANG
|
||||
# for related places search: ADD_NEW_LANG
|
||||
]
|
||||
|
||||
Preview:
|
||||
@@ -14953,8 +15045,6 @@ components:
|
||||
type: string
|
||||
authenticate:
|
||||
type: boolean
|
||||
base_endpoint:
|
||||
type: string
|
||||
required:
|
||||
- authenticate
|
||||
- base_endpoint
|
||||
@@ -14976,6 +15066,8 @@ components:
|
||||
$ref: "#/components/schemas/DeliveryType"
|
||||
delivery_config:
|
||||
$ref: "#/components/schemas/PushConfig"
|
||||
subscription_mode:
|
||||
$ref: "#/components/schemas/SubscriptionMode"
|
||||
last_server_ping:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -14989,7 +15081,9 @@ components:
|
||||
- subscription_id
|
||||
- enabled
|
||||
- delivery_type
|
||||
|
||||
- subscription_mode
|
||||
|
||||
|
||||
SubscriptionMode:
|
||||
type: string
|
||||
enum:
|
||||
@@ -14997,55 +15091,24 @@ components:
|
||||
- create_update
|
||||
description: "The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new subscription."
|
||||
|
||||
GcpExistingSubscription:
|
||||
|
||||
GcpTriggerData:
|
||||
type: object
|
||||
properties:
|
||||
gcp_resource_path:
|
||||
type: string
|
||||
subscription_mode:
|
||||
$ref: "#/components/schemas/SubscriptionMode"
|
||||
topic_id:
|
||||
type: string
|
||||
subscription_id:
|
||||
type: string
|
||||
base_endpoint:
|
||||
type: string
|
||||
required:
|
||||
- subscription_id
|
||||
- base_endpoint
|
||||
|
||||
GcpCreateUpdateSubscription:
|
||||
type: object
|
||||
properties:
|
||||
subscription_id:
|
||||
type: string
|
||||
delivery_type:
|
||||
$ref: "#/components/schemas/DeliveryType"
|
||||
delivery_config:
|
||||
$ref: "#/components/schemas/PushConfig"
|
||||
required:
|
||||
- delivery_type
|
||||
|
||||
|
||||
GcpSubscriptionModeConfig:
|
||||
type: object
|
||||
properties:
|
||||
subscription_mode:
|
||||
$ref: "#/components/schemas/SubscriptionMode"
|
||||
required:
|
||||
- subscription_mode
|
||||
allOf:
|
||||
- oneOf:
|
||||
- $ref: "#/components/schemas/GcpExistingSubscription"
|
||||
- $ref: "#/components/schemas/GcpCreateUpdateSubscription"
|
||||
description: |
|
||||
"This is a union type representing the subscription mode.
|
||||
- 'existing': Represents an existing GCP subscription, and should be accompanied by an 'ExistingGcpSubscription' object.
|
||||
- 'create_update': Represents a new or updated GCP subscription, and should be accompanied by a 'CreateUpdateConfig' object."
|
||||
|
||||
NewGcpTrigger:
|
||||
type: object
|
||||
properties:
|
||||
gcp_resource_path:
|
||||
type: string
|
||||
topic_id:
|
||||
type: string
|
||||
subscription_mode:
|
||||
$ref: "#/components/schemas/GcpSubscriptionModeConfig"
|
||||
path:
|
||||
type: string
|
||||
script_path:
|
||||
@@ -15062,34 +15125,6 @@ components:
|
||||
- topic_id
|
||||
- subscription_mode
|
||||
|
||||
EditGcpTrigger:
|
||||
type: object
|
||||
properties:
|
||||
gcp_resource_path:
|
||||
type: string
|
||||
topic_id:
|
||||
type: string
|
||||
subscription_mode:
|
||||
$ref: "#/components/schemas/GcpSubscriptionModeConfig"
|
||||
path:
|
||||
type: string
|
||||
script_path:
|
||||
type: string
|
||||
is_flow:
|
||||
type: boolean
|
||||
enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
- is_flow
|
||||
- enabled
|
||||
- mqtt_resource_path
|
||||
- subscription_id
|
||||
- delivery_type
|
||||
- topic_id
|
||||
- subscription_mode
|
||||
|
||||
GetAllTopicSubscription:
|
||||
type: object
|
||||
properties:
|
||||
@@ -15106,7 +15141,13 @@ components:
|
||||
type: string
|
||||
required:
|
||||
- subscription_id
|
||||
|
||||
|
||||
AwsAuthResourceType:
|
||||
type: string
|
||||
enum:
|
||||
- oidc
|
||||
- credentials
|
||||
|
||||
SqsTrigger:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/TriggerExtraProperty"
|
||||
@@ -15114,6 +15155,8 @@ components:
|
||||
properties:
|
||||
queue_url:
|
||||
type: string
|
||||
aws_auth_resource_type:
|
||||
$ref: "#/components/schemas/AwsAuthResourceType"
|
||||
aws_resource_path:
|
||||
type: string
|
||||
message_attributes:
|
||||
@@ -15134,12 +15177,15 @@ components:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
- enabled
|
||||
- aws_auth_resource_type
|
||||
|
||||
NewSqsTrigger:
|
||||
type: object
|
||||
properties:
|
||||
queue_url:
|
||||
type: string
|
||||
aws_auth_resource_type:
|
||||
$ref: "#/components/schemas/AwsAuthResourceType"
|
||||
aws_resource_path:
|
||||
type: string
|
||||
message_attributes:
|
||||
@@ -15160,12 +15206,15 @@ components:
|
||||
- path
|
||||
- script_path
|
||||
- is_flow
|
||||
- aws_auth_resource_type
|
||||
|
||||
EditSqsTrigger:
|
||||
type: object
|
||||
properties:
|
||||
queue_url:
|
||||
type: string
|
||||
aws_auth_resource_type:
|
||||
$ref: "#/components/schemas/AwsAuthResourceType"
|
||||
aws_resource_path:
|
||||
type: string
|
||||
message_attributes:
|
||||
@@ -15187,6 +15236,7 @@ components:
|
||||
- script_path
|
||||
- is_flow
|
||||
- enabled
|
||||
- aws_auth_resource_type
|
||||
|
||||
Slot:
|
||||
type: object
|
||||
@@ -15906,6 +15956,8 @@ components:
|
||||
execution_mode:
|
||||
type: string
|
||||
enum: [viewer, publisher, anonymous]
|
||||
raw_app:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- workspace_id
|
||||
@@ -16652,4 +16704,29 @@ components:
|
||||
presigned:
|
||||
type: string
|
||||
required:
|
||||
- s3
|
||||
- s3
|
||||
|
||||
TeamsChannel:
|
||||
type: object
|
||||
required:
|
||||
- team_id
|
||||
- team_name
|
||||
- channel_id
|
||||
- channel_name
|
||||
properties:
|
||||
team_id:
|
||||
type: string
|
||||
description: Microsoft Teams team ID
|
||||
minLength: 1
|
||||
team_name:
|
||||
type: string
|
||||
description: Microsoft Teams team name
|
||||
minLength: 1
|
||||
channel_id:
|
||||
type: string
|
||||
description: Microsoft Teams channel ID
|
||||
minLength: 1
|
||||
channel_name:
|
||||
type: string
|
||||
description: Microsoft Teams channel name
|
||||
minLength: 1
|
||||
@@ -21,8 +21,8 @@ pub fn workspaced_service(
|
||||
_base_internal_url: String,
|
||||
) -> (
|
||||
Router,
|
||||
Option<tokio::task::JoinHandle<()>>,
|
||||
windmill_worker::JobCompletedSender,
|
||||
Vec<tokio::task::JoinHandle<()>>,
|
||||
Option<windmill_worker::JobCompletedSender>,
|
||||
) {
|
||||
use windmill_common::worker::Connection;
|
||||
use windmill_worker::JobCompletedSender;
|
||||
@@ -32,7 +32,7 @@ pub fn workspaced_service(
|
||||
|
||||
let router = Router::new();
|
||||
|
||||
(router, None, job_completed_tx)
|
||||
(router, vec![], Some(job_completed_tx))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
|
||||
@@ -24,10 +24,10 @@ use crate::{
|
||||
},
|
||||
users::fetch_api_authed_from_permissioned_as,
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use axum::response::Response;
|
||||
use axum::{
|
||||
extract::{Extension, Json, Path, Query},
|
||||
body::Body,
|
||||
extract::{Extension, Json, Multipart, Path, Query},
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, post},
|
||||
Router,
|
||||
@@ -88,10 +88,13 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get/draft/*path", get(get_app_w_draft))
|
||||
.route("/secret_of/*path", get(get_secret_id))
|
||||
.route("/get/v/*id", get(get_app_by_id))
|
||||
.route("/get_data/v/*id", get(get_raw_app_data))
|
||||
.route("/exists/*path", get(exists_app))
|
||||
.route("/update/*path", post(update_app))
|
||||
.route("/update_raw/*path", post(update_app_raw))
|
||||
.route("/delete/*path", delete(delete_app))
|
||||
.route("/create", post(create_app))
|
||||
.route("/create_raw", post(create_app_raw))
|
||||
.route("/history/p/*path", get(get_app_history))
|
||||
.route("/get_latest_version/*path", get(get_latest_version))
|
||||
.route("/history_update/a/:id/v/:version", post(update_app_history))
|
||||
@@ -135,6 +138,12 @@ pub struct ListableApp {
|
||||
#[sqlx(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
#[serde(skip_serializing_if = "is_false")]
|
||||
pub raw_app: bool,
|
||||
}
|
||||
|
||||
fn is_false(b: &bool) -> bool {
|
||||
!b
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Deserialize)]
|
||||
@@ -328,7 +337,8 @@ async fn list_apps(
|
||||
"app.extra_perms",
|
||||
"favorite.path IS NOT NULL as starred",
|
||||
"draft.path IS NOT NULL as has_draft",
|
||||
"draft_only"
|
||||
"draft_only",
|
||||
"app_version.raw_app",
|
||||
])
|
||||
.left()
|
||||
.join("favorite")
|
||||
@@ -387,6 +397,44 @@ async fn list_apps(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
async fn get_raw_app_data(Path((w_id, version_id)): Path<(String, String)>) -> Result<Response> {
|
||||
let file_path = format!("/tmp/wmill/{}/{}", w_id, version_id);
|
||||
let file = tokio::fs::File::open(file_path).await?;
|
||||
let stream = tokio_util::io::ReaderStream::new(file);
|
||||
let res = Response::builder().header(
|
||||
http::header::CONTENT_TYPE,
|
||||
if version_id.ends_with(".css") {
|
||||
"text/css"
|
||||
} else {
|
||||
"text/javascript"
|
||||
},
|
||||
);
|
||||
Ok(res.body(Body::from_stream(stream)).unwrap())
|
||||
}
|
||||
|
||||
// async fn get_app_version(
|
||||
// authed: ApiAuthed,
|
||||
// Extension(user_db): Extension<UserDB>,
|
||||
// Path((w_id, path)): Path<(String, StripPath)>,
|
||||
// ) -> JsonResult<i64> {
|
||||
// let path = path.to_path();
|
||||
// let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
// let version_o = sqlx::query_scalar!(
|
||||
// "SELECT app.versions[array_upper(app.versions, 1)] as version FROM app
|
||||
// WHERE app.path = $1 AND app.workspace_id = $2",
|
||||
// path,
|
||||
// &w_id,
|
||||
// )
|
||||
// .fetch_optional(&mut *tx)
|
||||
// .await?
|
||||
// .flatten();
|
||||
// tx.commit().await?;
|
||||
|
||||
// let version = not_found_if_none(version_o, "App", path)?;
|
||||
// Ok(Json(version))
|
||||
// }
|
||||
|
||||
async fn get_app(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -727,6 +775,97 @@ async fn get_secret_id(
|
||||
Ok(hx)
|
||||
}
|
||||
|
||||
macro_rules! process_app_multipart {
|
||||
($authed:expr, $user_db:expr, $db:expr, $w_id:expr, $path:expr, $multipart:expr, $internal_fn:expr) => {
|
||||
async {
|
||||
let mut saved_app = None;
|
||||
let mut uploaded_js = false;
|
||||
|
||||
//todo: use s3 instead
|
||||
let file_path = format!("/tmp/wmill/{}", $w_id);
|
||||
std::fs::create_dir_all(&file_path).unwrap();
|
||||
|
||||
let mut multipart = $multipart;
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
if name == "app" {
|
||||
let app = serde_json::from_slice(&data).map_err(to_anyhow)?;
|
||||
let (ntx, npath, nid) = $internal_fn(
|
||||
$authed.clone(),
|
||||
$db.clone(),
|
||||
$user_db.clone(),
|
||||
$w_id,
|
||||
$path,
|
||||
true,
|
||||
app,
|
||||
)
|
||||
.await?;
|
||||
saved_app = Some((npath, nid, ntx));
|
||||
} else if name == "js" {
|
||||
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
|
||||
let file_path = format!("{}/{}.js", file_path, id);
|
||||
std::fs::write(file_path, data).unwrap();
|
||||
uploaded_js = true;
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"App payload need to be created first".to_string(),
|
||||
));
|
||||
}
|
||||
} else if name == "css" {
|
||||
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
|
||||
let file_path = format!("{}/{}.css", file_path, id);
|
||||
std::fs::write(file_path, data).unwrap();
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"App payload need to be created first".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadRequest(format!("Unsupported field: {}", name)));
|
||||
}
|
||||
}
|
||||
if !uploaded_js {
|
||||
return Err(Error::BadRequest("js or css file not uploaded".to_string()));
|
||||
}
|
||||
if let Some((npath, id, tx)) = saved_app {
|
||||
tx.commit().await?;
|
||||
Ok((npath, id))
|
||||
} else {
|
||||
Err(Error::BadRequest("App not created".to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn create_app_raw<'a>(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
multipart: Multipart,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let (path, _id) = process_app_multipart!(
|
||||
authed,
|
||||
user_db,
|
||||
db,
|
||||
&w_id,
|
||||
"",
|
||||
multipart,
|
||||
|authed, db, user_db, w_id, _path, raw_app, app| create_app_internal(
|
||||
authed, db, user_db, w_id, raw_app, app
|
||||
)
|
||||
)
|
||||
.await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateApp { workspace: w_id, path: path.clone() },
|
||||
);
|
||||
Ok((StatusCode::CREATED, path))
|
||||
}
|
||||
|
||||
async fn list_paths_from_workspace_runnable(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -755,17 +894,36 @@ async fn create_app(
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(mut app): Json<CreateApp>,
|
||||
Json(app): Json<CreateApp>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let path = app.path.clone();
|
||||
let (new_tx, _path, _id) = create_app_internal(authed, db, user_db, &w_id, false, app).await?;
|
||||
|
||||
new_tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateApp { workspace: w_id, path: path.clone() },
|
||||
);
|
||||
|
||||
Ok((StatusCode::CREATED, path))
|
||||
}
|
||||
|
||||
async fn create_app_internal<'a>(
|
||||
authed: ApiAuthed,
|
||||
db: sqlx::Pool<sqlx::Postgres>,
|
||||
user_db: UserDB,
|
||||
w_id: &String,
|
||||
raw_app: bool,
|
||||
mut app: CreateApp,
|
||||
) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> {
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
app.policy.on_behalf_of_email = Some(authed.email.clone());
|
||||
|
||||
let path = app.path.clone();
|
||||
if &app.path == "" {
|
||||
return Err(Error::BadRequest("App path cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM app WHERE path = $1 AND workspace_id = $2)",
|
||||
&app.path,
|
||||
@@ -774,21 +932,19 @@ async fn create_app(
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if exists {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"App with path {} already exists",
|
||||
&app.path
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(custom_path) = &app.custom_path {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))",
|
||||
custom_path,
|
||||
if *CLOUD_HOSTED { Some(&w_id) } else { None }
|
||||
if *CLOUD_HOSTED { Some(w_id) } else { None }
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?.unwrap_or(false);
|
||||
@@ -800,7 +956,6 @@ async fn create_app(
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
|
||||
&app.path,
|
||||
@@ -808,7 +963,6 @@ async fn create_app(
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let id = sqlx::query_scalar!(
|
||||
"INSERT INTO app
|
||||
(workspace_id, path, summary, policy, versions, draft_only, custom_path)
|
||||
@@ -819,24 +973,24 @@ async fn create_app(
|
||||
json!(app.policy),
|
||||
app.draft_only,
|
||||
app.custom_path
|
||||
.as_ref()
|
||||
.map(|s| if s.is_empty() { None } else { Some(s) })
|
||||
.flatten()
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let v_id = sqlx::query_scalar!(
|
||||
"INSERT INTO app_version
|
||||
(app_id, value, created_by)
|
||||
VALUES ($1, $2::text::json, $3) RETURNING id",
|
||||
(app_id, value, created_by, raw_app)
|
||||
VALUES ($1, $2::text::json, $3, $4) RETURNING id",
|
||||
id,
|
||||
//to preserve key orders
|
||||
serde_json::to_string(&app.value).unwrap(),
|
||||
authed.username,
|
||||
raw_app
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE app SET versions = array_append(versions, $1::bigint) WHERE id = $2",
|
||||
v_id,
|
||||
@@ -850,22 +1004,20 @@ async fn create_app(
|
||||
&authed,
|
||||
"apps.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
w_id,
|
||||
Some(&app.path),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
|
||||
if let Some(dm) = app.deployment_message {
|
||||
if let Some(dm) = &app.deployment_message {
|
||||
args.insert("deployment_message".to_string(), to_raw_value(&dm));
|
||||
}
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
w_id,
|
||||
JobPayload::AppDependencies { path: app.path.clone(), version: v_id },
|
||||
PushArgs { args: &args, extra: None },
|
||||
&authed.username,
|
||||
@@ -889,14 +1041,7 @@ async fn create_app(
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
|
||||
new_tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateApp { workspace: w_id, path: app.path.clone() },
|
||||
);
|
||||
|
||||
Ok((StatusCode::CREATED, app.path))
|
||||
Ok((new_tx, path, v_id))
|
||||
}
|
||||
|
||||
async fn list_hub_apps(Extension(db): Extension<DB>) -> impl IntoResponse {
|
||||
@@ -1017,12 +1162,76 @@ async fn update_app(
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(ns): Json<EditApp>,
|
||||
) -> Result<String> {
|
||||
use sql_builder::prelude::*;
|
||||
|
||||
// create_app_internal(authed, user_db, db, &w_id, &mut app).await?;
|
||||
let path = path.to_path();
|
||||
let opath = path.to_string();
|
||||
let (new_tx, npath, _v_id) =
|
||||
update_app_internal(authed, db, user_db, &w_id, path, false, ns).await?;
|
||||
new_tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateApp {
|
||||
workspace: w_id.clone(),
|
||||
old_path: opath.clone(),
|
||||
new_path: npath.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("app {} updated (npath: {:?})", opath, npath))
|
||||
}
|
||||
|
||||
async fn update_app_raw<'a>(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
multipart: Multipart,
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
let opath = path.to_string();
|
||||
let (npath, _id) = process_app_multipart!(
|
||||
authed,
|
||||
user_db,
|
||||
db,
|
||||
&w_id,
|
||||
path,
|
||||
multipart,
|
||||
update_app_internal
|
||||
)
|
||||
.await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateApp {
|
||||
workspace: w_id.clone(),
|
||||
old_path: opath.to_owned(),
|
||||
new_path: npath.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("app {} updated (npath: {:?})", opath, npath))
|
||||
}
|
||||
// async fn create_app_internal<'a>(
|
||||
// authed: ApiAuthed,
|
||||
// db: sqlx::Pool<sqlx::Postgres>,
|
||||
// user_db: UserDB,
|
||||
// w_id: &String,
|
||||
// app: &mut CreateApp,
|
||||
// )
|
||||
|
||||
async fn update_app_internal<'a>(
|
||||
authed: ApiAuthed,
|
||||
db: sqlx::Pool<sqlx::Postgres>,
|
||||
user_db: UserDB,
|
||||
w_id: &str,
|
||||
path: &str,
|
||||
raw_app: bool,
|
||||
ns: EditApp,
|
||||
) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> {
|
||||
use sql_builder::prelude::*;
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
let npath = if ns.policy.is_some()
|
||||
|| ns.path.is_some()
|
||||
|| ns.summary.is_some()
|
||||
@@ -1069,7 +1278,7 @@ async fn update_app(
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))",
|
||||
ncustom_path,
|
||||
if *CLOUD_HOSTED { Some(&w_id) } else { None },
|
||||
if *CLOUD_HOSTED { Some(w_id) } else { None },
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
@@ -1116,12 +1325,13 @@ async fn update_app(
|
||||
|
||||
let v_id = sqlx::query_scalar!(
|
||||
"INSERT INTO app_version
|
||||
(app_id, value, created_by)
|
||||
VALUES ($1, $2::text::json, $3) RETURNING id",
|
||||
(app_id, value, created_by, raw_app)
|
||||
VALUES ($1, $2::text::json, $3, $4) RETURNING id",
|
||||
app_id,
|
||||
//to preserve key orders
|
||||
serde_json::to_string(&nvalue).unwrap(),
|
||||
authed.username,
|
||||
raw_app
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
@@ -1152,7 +1362,6 @@ async fn update_app(
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
|
||||
path,
|
||||
@@ -1160,29 +1369,26 @@ async fn update_app(
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"apps.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
w_id,
|
||||
Some(&npath),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
|
||||
if let Some(dm) = ns.deployment_message {
|
||||
args.insert("deployment_message".to_string(), to_raw_value(&dm));
|
||||
}
|
||||
args.insert("parent_path".to_string(), to_raw_value(&path));
|
||||
|
||||
let (dependency_job_uuid, new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
w_id,
|
||||
JobPayload::AppDependencies { path: npath.clone(), version: v_id },
|
||||
PushArgs { args: &args, extra: None },
|
||||
&authed.username,
|
||||
@@ -1205,18 +1411,7 @@ async fn update_app(
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
new_tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateApp {
|
||||
workspace: w_id,
|
||||
old_path: path.to_owned(),
|
||||
new_path: npath.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("app {} updated (npath: {:?})", path, npath))
|
||||
Ok((new_tx, npath, v_id))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
||||
@@ -511,6 +511,13 @@ where
|
||||
let path_vec: Vec<&str> = original_uri.path().split("/").collect();
|
||||
let workspace_id = if path_vec.len() >= 4 && path_vec[0] == "" && path_vec[2] == "w" {
|
||||
Some(path_vec[3].to_owned())
|
||||
} else if path_vec.len() >= 5
|
||||
&& path_vec[0] == ""
|
||||
&& path_vec[1] == "api"
|
||||
&& path_vec[2] == "mcp"
|
||||
&& path_vec[3] == "w"
|
||||
{
|
||||
Some(path_vec[4].to_string())
|
||||
} else {
|
||||
if path_vec.len() >= 5
|
||||
&& path_vec[0] == ""
|
||||
|
||||
@@ -18,16 +18,22 @@ use {
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
use crate::gcp_triggers_ee::{
|
||||
manage_google_subscription, process_google_push_request, validate_jwt_token, SubscriptionMode,
|
||||
manage_google_subscription, process_google_push_request, validate_jwt_token,
|
||||
CreateUpdateConfig, SubscriptionMode,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
|
||||
use windmill_common::auth::aws::AwsAuthResourceType;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "http_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
))]
|
||||
use {
|
||||
axum::extract::Request, http::HeaderMap, serde::de::DeserializeOwned,
|
||||
windmill_common::error::Error,
|
||||
axum::extract::Request,
|
||||
http::HeaderMap,
|
||||
serde::de::DeserializeOwned,
|
||||
windmill_common::{error::Error, utils::empty_string_as_none},
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
@@ -150,14 +156,20 @@ pub struct SqsTriggerConfig {
|
||||
pub queue_url: String,
|
||||
pub aws_resource_path: String,
|
||||
pub message_attributes: Option<Vec<String>>,
|
||||
pub aws_auth_resource_type: AwsAuthResourceType,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GcpTriggerConfig {
|
||||
pub gcp_resource_path: String,
|
||||
#[serde(flatten)]
|
||||
pub subscription_mode: SubscriptionMode,
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
pub subscription_id: Option<String>,
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
pub base_endpoint: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub create_update: Option<CreateUpdateConfig>,
|
||||
pub topic_id: String,
|
||||
}
|
||||
|
||||
@@ -366,11 +378,15 @@ async fn set_gcp_trigger_config(
|
||||
&gcp_config.gcp_resource_path,
|
||||
&capture_config.path,
|
||||
&gcp_config.topic_id,
|
||||
&mut gcp_config.subscription_id,
|
||||
&mut gcp_config.base_endpoint,
|
||||
gcp_config.subscription_mode,
|
||||
gcp_config.create_update,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
gcp_config.subscription_mode = SubscriptionMode::CreateUpdate(config);
|
||||
gcp_config.create_update = Some(config);
|
||||
gcp_config.subscription_mode = SubscriptionMode::CreateUpdate;
|
||||
capture_config.trigger_config = Some(TriggerConfig::Gcp(gcp_config));
|
||||
|
||||
Ok(capture_config)
|
||||
@@ -887,7 +903,7 @@ async fn gcp_payload(
|
||||
|
||||
let authed = fetch_api_authed(owner.clone(), email, &w_id, &db, None).await?;
|
||||
|
||||
let SubscriptionMode::CreateUpdate(config) = &gcp_trigger_config.subscription_mode else {
|
||||
let Some(config) = &gcp_trigger_config.create_update else {
|
||||
return Err(Error::BadConfig("Bad config".to_string()));
|
||||
};
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ impl ModelInstance {
|
||||
let token_ids = Tensor::new(&tokens[..], &Device::Cpu)?.unsqueeze(0)?;
|
||||
let token_type_ids = token_ids.zeros_like()?;
|
||||
|
||||
let embedding = self.model.forward(&token_ids, &token_type_ids)?;
|
||||
let embedding = self.model.forward(&token_ids, &token_type_ids, None)?;
|
||||
let embedding = (embedding.sum(1)? / embedding.dim(1)? as f64)?;
|
||||
let embedding = normalize_l2(&embedding)?;
|
||||
|
||||
|
||||
@@ -52,11 +52,12 @@ pub struct ExistingGcpSubscription {
|
||||
pub base_endpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "subscription_mode", rename_all = "snake_case")]
|
||||
#[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(ExistingGcpSubscription),
|
||||
CreateUpdate(CreateUpdateConfig),
|
||||
Existing,
|
||||
CreateUpdate,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -77,7 +78,11 @@ pub async fn manage_google_subscription(
|
||||
_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,
|
||||
) -> WindmillResult<CreateUpdateConfig> {
|
||||
Ok(CreateUpdateConfig::default())
|
||||
}
|
||||
@@ -117,6 +122,7 @@ pub struct GcpTrigger {
|
||||
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,
|
||||
|
||||
@@ -253,8 +253,8 @@ mod zoom {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
struct ZoomPayload {
|
||||
#[serde(rename = "plainToken")]
|
||||
plain_token: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -674,14 +674,14 @@ async fn list_selected_job_groups(
|
||||
'kind', jb.kind,
|
||||
'script_path', jb.runnable_path,
|
||||
'latest_schema', COALESCE(
|
||||
(SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),
|
||||
(SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow')
|
||||
(SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),
|
||||
(SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow')
|
||||
),
|
||||
'schemas', ARRAY(
|
||||
SELECT jsonb_build_object(
|
||||
'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),
|
||||
'job_ids', ARRAY_AGG(DISTINCT j.id),
|
||||
'schema', ANY_VALUE(COALESCE(s.schema, f.schema))
|
||||
'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1]
|
||||
) FROM v2_job j
|
||||
LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'
|
||||
LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'
|
||||
@@ -3441,7 +3441,11 @@ async fn batch_rerun_handle_job(
|
||||
}
|
||||
InputTransform::Javascript { expr } => {
|
||||
#[cfg(not(feature = "deno_core"))]
|
||||
tracing::error!("deno_core feature is not activated, cannot evaluate: {expr}");
|
||||
Err(error::Error::ExecutionErr(
|
||||
format!("deno_core feature is not activated, cannot evaluate: {expr}")
|
||||
.to_string(),
|
||||
))?;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
args.insert(
|
||||
property_name.clone(),
|
||||
@@ -4618,7 +4622,6 @@ pub async fn run_wait_result_script_by_hash(
|
||||
check_license_key_valid().await?;
|
||||
|
||||
let args = args.to_push_args_owned(&authed, &db, &w_id).await?;
|
||||
|
||||
check_queue_too_long(&db, run_query.queue_limit).await?;
|
||||
|
||||
let hash = script_hash.0;
|
||||
@@ -5835,7 +5838,7 @@ pub fn filter_list_completed_query(
|
||||
if let Some(label) = &lq.label {
|
||||
if lq.allow_wildcards.unwrap_or(false) {
|
||||
let wh = format!(
|
||||
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE label LIKE '{}')",
|
||||
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE jsonb_typeof(result->'wm_labels') = 'array' AND label LIKE '{}')",
|
||||
&label.replace("*", "%").replace("'", "''")
|
||||
);
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
|
||||
@@ -18,6 +18,8 @@ use crate::oauth2_ee::SlackVerifier;
|
||||
#[cfg(feature = "smtp")]
|
||||
use crate::smtp_server_ee::SmtpServer;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
use crate::mcp::{setup_mcp_server, Runner as McpRunner};
|
||||
use crate::tracing_init::MyOnFailure;
|
||||
use crate::{
|
||||
tracing_init::{MyMakeSpan, MyOnResponse},
|
||||
@@ -27,6 +29,7 @@ use crate::{
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
use agent_workers_ee::AgentCache;
|
||||
|
||||
use anyhow::Context;
|
||||
use argon2::Argon2;
|
||||
use axum::extract::DefaultBodyLimit;
|
||||
@@ -139,6 +142,9 @@ mod workspaces_ee;
|
||||
mod workspaces_export;
|
||||
mod workspaces_extra;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
mod mcp;
|
||||
|
||||
pub const DEFAULT_BODY_LIMIT: usize = 2097152 * 100; // 200MB
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -219,6 +225,7 @@ pub async fn run_server(
|
||||
mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
port_tx: tokio::sync::oneshot::Sender<String>,
|
||||
server_mode: bool,
|
||||
mcp_mode: bool,
|
||||
_base_internal_url: String,
|
||||
) -> anyhow::Result<()> {
|
||||
let user_db = UserDB::new(db.clone());
|
||||
@@ -404,7 +411,7 @@ pub async fn run_server(
|
||||
Router::new()
|
||||
};
|
||||
|
||||
if !*CLOUD_HOSTED && server_mode {
|
||||
if !*CLOUD_HOSTED && server_mode && !mcp_mode {
|
||||
#[cfg(feature = "websocket")]
|
||||
{
|
||||
let ws_killpill_rx = killpill_rx.resubscribe();
|
||||
@@ -448,9 +455,41 @@ pub async fn run_server(
|
||||
}
|
||||
}
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
.context("binding main windmill server")?;
|
||||
let port = listener.local_addr().map(|x| x.port()).unwrap_or(8000);
|
||||
let ip = listener
|
||||
.local_addr()
|
||||
.map(|x| x.ip().to_string())
|
||||
.unwrap_or("localhost".to_string());
|
||||
|
||||
// Setup MCP server
|
||||
#[allow(unused_variables)]
|
||||
let (mcp_router, mcp_main_ct, mcp_service_ct) = {
|
||||
#[cfg(feature = "mcp")]
|
||||
if server_mode || mcp_mode {
|
||||
let (mcp_sse_server, mcp_router) = setup_mcp_server(addr, "/api/mcp/w/:workspace_id")?;
|
||||
#[cfg(feature = "mcp")]
|
||||
let mcp_main_ct = mcp_sse_server.config.ct.clone(); // Token to signal shutdown *to* MCP
|
||||
#[cfg(feature = "mcp")]
|
||||
let mcp_service_ct = mcp_sse_server.with_service(McpRunner::new); // Token to wait for MCP *service* shutdown
|
||||
(mcp_router, Some(mcp_main_ct), Some(mcp_service_ct))
|
||||
} else {
|
||||
(Router::new(), None, None)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mcp"))]
|
||||
(Router::new(), None::<()>, None::<()>)
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_killpill_tx) =
|
||||
agent_workers_ee::workspaced_service(db.clone(), _base_internal_url.clone());
|
||||
if server_mode {
|
||||
agent_workers_ee::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
} else {
|
||||
(Router::new(), vec![], None)
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
let agent_cache = Arc::new(AgentCache::new());
|
||||
@@ -531,26 +570,6 @@ pub async fn run_server(
|
||||
.nest("/ai", ai::global_service())
|
||||
.route_layer(from_extractor::<ApiAuthed>())
|
||||
.route_layer(from_extractor::<users::Tokened>())
|
||||
.nest("/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_ee::global_service().layer(Extension(agent_cache.clone()))
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.nest("/w/:workspace_id/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_router.layer(Extension(agent_cache.clone()))
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.nest("/jobs", jobs::global_root_service())
|
||||
.nest(
|
||||
"/srch/w/:workspace_id/index",
|
||||
@@ -586,6 +605,28 @@ pub async fn run_server(
|
||||
.layer(from_extractor::<OptAuthed>())
|
||||
.layer(cors.clone()),
|
||||
)
|
||||
.nest("/mcp/w/:workspace_id", mcp_router)
|
||||
.layer(from_extractor::<OptAuthed>())
|
||||
.nest("/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_ee::global_service().layer(Extension(agent_cache.clone()))
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.nest("/w/:workspace_id/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_router.layer(Extension(agent_cache.clone()))
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.nest(
|
||||
"/w/:workspace_id/jobs_u",
|
||||
jobs::workspace_unauthed_service().layer(cors.clone()),
|
||||
@@ -694,14 +735,6 @@ pub async fn run_server(
|
||||
.on_failure(MyOnFailure {}),
|
||||
)
|
||||
};
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
.context("binding main windmill server")?;
|
||||
let port = listener.local_addr().map(|x| x.port()).unwrap_or(8000);
|
||||
let ip = listener
|
||||
.local_addr()
|
||||
.map(|x| x.ip().to_string())
|
||||
.unwrap_or("localhost".to_string());
|
||||
|
||||
let server = axum::serve(listener, app.into_make_service());
|
||||
|
||||
@@ -719,14 +752,28 @@ pub async fn run_server(
|
||||
let server = server.with_graceful_shutdown(async move {
|
||||
killpill_rx.recv().await.ok();
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
if let Err(e) = agent_workers_killpill_tx.kill().await {
|
||||
tracing::error!("Error killing agent workers: {e:#}");
|
||||
if let Some(agent_workers_killpill_tx) = agent_workers_killpill_tx {
|
||||
if let Err(e) = agent_workers_killpill_tx.kill().await {
|
||||
tracing::error!("Error killing agent workers: {e:#}");
|
||||
}
|
||||
}
|
||||
tracing::info!("Graceful shutdown of server");
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
{
|
||||
if let Some(mcp_main_ct) = mcp_main_ct {
|
||||
tracing::info!("Received shutdown signal, cancelling MCP server...");
|
||||
mcp_main_ct.cancel();
|
||||
}
|
||||
if let Some(mcp_service_ct) = mcp_service_ct {
|
||||
tracing::info!("Waiting for MCP service cancellation...");
|
||||
mcp_service_ct.cancelled().await;
|
||||
tracing::info!("MCP service cancelled.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.await?;
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
for (i, bg_processor) in agent_workers_bg_processor.into_iter().enumerate() {
|
||||
tracing::info!("server off. shutting down agent worker bg processor {i}");
|
||||
|
||||
1143
backend/windmill-api/src/mcp.rs
Normal file
1143
backend/windmill-api/src/mcp.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,10 +30,9 @@ use windmill_common::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
create_logical_replication_slot_query, create_publication_query,
|
||||
drop_logical_replication_slot_query, drop_publication_query, generate_random_string,
|
||||
get_database_connection, get_raw_postgres_connection, ERROR_PUBLICATION_NAME_NOT_EXISTS,
|
||||
ERROR_REPLICATION_SLOT_NOT_EXISTS,
|
||||
create_logical_replication_slot_query, create_publication_query, drop_publication_query,
|
||||
generate_random_string, get_database_connection, get_raw_postgres_connection,
|
||||
ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
@@ -396,7 +395,7 @@ pub async fn create_postgres_trigger(
|
||||
"Missing replication slot name".to_string(),
|
||||
));
|
||||
}
|
||||
(publication_name.unwrap(), replication_slot_name.unwrap(), )
|
||||
(publication_name.unwrap(), replication_slot_name.unwrap())
|
||||
};
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
@@ -124,11 +124,13 @@ export async function main(
|
||||
transaction_type: "insert" | "update" | "delete",
|
||||
schema_name: string,
|
||||
table_name: string,
|
||||
row: {}
|
||||
row: {},
|
||||
old_row?: {}
|
||||
) {{
|
||||
}}
|
||||
"#,
|
||||
struct_definition
|
||||
&struct_definition,
|
||||
&struct_definition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ impl RelationConverter {
|
||||
.ok_or(RelationConversionError::FailToFindMatchingTable)
|
||||
}
|
||||
|
||||
pub fn body_to_json(
|
||||
pub fn row_to_json(
|
||||
&self,
|
||||
to_decode: (Oid, Vec<TupleData>),
|
||||
) -> Result<Map<String, Value>, RelationConversionError> {
|
||||
|
||||
@@ -650,37 +650,79 @@ async fn listen_to_transactions(
|
||||
None
|
||||
}
|
||||
Insert(insert) => {
|
||||
Some((insert.o_id, relations.body_to_json((insert.o_id, insert.tuple)), "insert"))
|
||||
Some((insert.o_id, Ok(None), relations.row_to_json((insert.o_id, insert.tuple)), "insert"))
|
||||
}
|
||||
Update(update) => {
|
||||
Some((update.o_id, relations.body_to_json((update.o_id, update.new_tuple)), "update"))
|
||||
let old_row = update.old_tuple.map(|old_tuple| relations.row_to_json((update.o_id, old_tuple))).transpose();
|
||||
let row = relations.row_to_json((update.o_id, update.new_tuple));
|
||||
Some((update.o_id, old_row, row, "update"))
|
||||
}
|
||||
Delete(delete) => {
|
||||
let body = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap());
|
||||
Some((delete.o_id, relations.body_to_json((delete.o_id, body)), "delete"))
|
||||
let row = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap());
|
||||
Some((delete.o_id, Ok(None), relations.row_to_json((delete.o_id, row)), "delete"))
|
||||
}
|
||||
};
|
||||
if let Some((o_id, Ok(body), transaction_type)) = json {
|
||||
let relation = match relations.get_relation(o_id) {
|
||||
Ok(relation) => relation,
|
||||
Err(err) => {
|
||||
tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string());
|
||||
continue;
|
||||
match json {
|
||||
Some((o_id, Ok(old_row), Ok(row), transaction_type)) => {
|
||||
let relation = match relations.get_relation(o_id) {
|
||||
Ok(relation) => relation,
|
||||
Err(err) => {
|
||||
tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let database_info = HashMap::from([
|
||||
("schema_name".to_string(), to_raw_value(&relation.namespace)),
|
||||
("table_name".to_string(), to_raw_value(&relation.name)),
|
||||
("transaction_type".to_string(), to_raw_value(&transaction_type)),
|
||||
("old_row".to_string(), to_raw_value(&old_row)),
|
||||
("row".to_string(), to_raw_value(&row)),
|
||||
]);
|
||||
let extra = Some(HashMap::from([(
|
||||
"wm_trigger".to_string(),
|
||||
to_raw_value(&serde_json::json!({"kind": "postgres", })),
|
||||
)]));
|
||||
|
||||
|
||||
let _ = pg.handle(&db, Some(database_info), extra).await;
|
||||
}
|
||||
Some((o_id, old_row, row, transaction_type)) => {
|
||||
let relation = match relations.get_relation(o_id) {
|
||||
Ok(relation) => relation,
|
||||
Err(err) => {
|
||||
tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = old_row {
|
||||
tracing::error!(
|
||||
transaction_type = ?transaction_type,
|
||||
schema = %relation.namespace,
|
||||
table = %relation.name,
|
||||
error = %err,
|
||||
"Failed to decode OLD row for {} transaction on {}.{}",
|
||||
transaction_type,
|
||||
relation.namespace,
|
||||
relation.name,
|
||||
);
|
||||
}
|
||||
};
|
||||
let database_info = HashMap::from([
|
||||
("schema_name".to_string(), to_raw_value(&relation.namespace)),
|
||||
("table_name".to_string(), to_raw_value(&relation.name)),
|
||||
("transaction_type".to_string(), to_raw_value(&transaction_type)),
|
||||
("row".to_string(), to_raw_value(&body)),
|
||||
]);
|
||||
let extra = Some(HashMap::from([(
|
||||
"wm_trigger".to_string(),
|
||||
to_raw_value(&serde_json::json!({"kind": "postgres", })),
|
||||
)]));
|
||||
|
||||
|
||||
let _ = pg.handle(&db, Some(database_info), extra).await;
|
||||
|
||||
if let Err(err) = row {
|
||||
tracing::error!(
|
||||
transaction_type = ?transaction_type,
|
||||
schema = %relation.namespace,
|
||||
table = %relation.name,
|
||||
error = %err,
|
||||
"Failed to decode NEW row for {} transaction on {}.{}",
|
||||
transaction_type,
|
||||
relation.namespace,
|
||||
relation.name,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ use std::{
|
||||
};
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_worker::process_relative_imports;
|
||||
|
||||
use windmill_common::error::to_anyhow;
|
||||
|
||||
@@ -264,9 +265,12 @@ async fn list_scripts(
|
||||
|
||||
if lq.show_archived.unwrap_or(false) {
|
||||
sqlb.and_where_eq(
|
||||
"o.created_at",
|
||||
"(select max(created_at) from script where o.path = path
|
||||
AND workspace_id = ?)"
|
||||
"o.ctid",
|
||||
"(SELECT ctid FROM script
|
||||
WHERE path = o.path
|
||||
AND workspace_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1)"
|
||||
.bind(&w_id),
|
||||
);
|
||||
sqlb.and_where_eq("archived", true);
|
||||
@@ -649,8 +653,13 @@ async fn create_script_internal<'c>(
|
||||
) {
|
||||
Some(String::new())
|
||||
} else {
|
||||
ns.lock
|
||||
.and_then(|e| if e.is_empty() { None } else { Some(e) })
|
||||
ns.lock.as_ref().and_then(|e| {
|
||||
if e.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(e.to_string())
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let needs_lock_gen = lock.is_none() && codebase.is_none();
|
||||
@@ -894,6 +903,40 @@ async fn create_script_internal<'c>(
|
||||
.await?;
|
||||
Ok((hash, new_tx))
|
||||
} else {
|
||||
let db2 = db.clone();
|
||||
let w_id2 = w_id.clone();
|
||||
let authed2 = authed.clone();
|
||||
let permissioned_as2 = permissioned_as.clone();
|
||||
let script_path2 = script_path.clone();
|
||||
let parent_path = p_path_opt.clone();
|
||||
let lock = ns.lock.clone();
|
||||
let deployment_message = ns.deployment_message.clone();
|
||||
let content = ns.content.clone();
|
||||
let language = ns.language.clone();
|
||||
tokio::spawn(async move {
|
||||
// wait for 10 seconds to make sure the script is deployed and that the CLI sync that pushed it (f one) is complete
|
||||
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
||||
if let Err(e) = process_relative_imports(
|
||||
&db2,
|
||||
None,
|
||||
None,
|
||||
&w_id2,
|
||||
&script_path2,
|
||||
parent_path,
|
||||
deployment_message,
|
||||
&content,
|
||||
&Some(language),
|
||||
&authed2.email,
|
||||
&authed2.username,
|
||||
&permissioned_as2,
|
||||
lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%e, "error processing relative imports");
|
||||
}
|
||||
});
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
@@ -949,7 +992,7 @@ async fn get_script_by_path(
|
||||
AND favorite.usr = $3
|
||||
WHERE s.path = $1
|
||||
AND s.workspace_id = $2
|
||||
AND s.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)",
|
||||
ORDER BY s.created_at DESC LIMIT 1",
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
@@ -958,9 +1001,7 @@ async fn get_script_by_path(
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as::<_, ScriptWithStarred>(
|
||||
"SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 \
|
||||
AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
|
||||
workspace_id = $2)",
|
||||
"SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
@@ -1000,9 +1041,8 @@ async fn get_script_by_path_w_draft(
|
||||
let script_o = sqlx::query_as::<_, ScriptWDraft>(
|
||||
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, has_preprocessor, on_behalf_of_email FROM script LEFT JOIN draft ON
|
||||
script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script'
|
||||
WHERE script.path = $1 AND script.workspace_id = $2 \
|
||||
AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
|
||||
workspace_id = $2)",
|
||||
WHERE script.path = $1 AND script.workspace_id = $2
|
||||
ORDER BY script.created_at DESC LIMIT 1",
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
@@ -1024,7 +1064,7 @@ async fn get_script_history(
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
|
||||
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
|
||||
WHERE s.workspace_id = $1 AND s.path = $2
|
||||
ORDER by created_at DESC",
|
||||
ORDER by s.created_at DESC",
|
||||
w_id,
|
||||
path.to_path(),
|
||||
)
|
||||
@@ -1052,7 +1092,7 @@ async fn get_latest_version(
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
|
||||
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
|
||||
WHERE s.workspace_id = $1 AND s.path = $2
|
||||
ORDER by created_at DESC",
|
||||
ORDER by s.created_at DESC LIMIT 1",
|
||||
w_id,
|
||||
path.to_path(),
|
||||
)
|
||||
@@ -1148,7 +1188,15 @@ async fn toggle_workspace_error_handler(
|
||||
match error_handler_maybe {
|
||||
Some(_) => {
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)",
|
||||
"UPDATE script
|
||||
SET ws_error_handler_muted = $3
|
||||
WHERE ctid = (
|
||||
SELECT ctid FROM script
|
||||
WHERE path = $1 AND workspace_id = $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
)
|
||||
",
|
||||
path.to_path(),
|
||||
w_id,
|
||||
req.muted,
|
||||
@@ -1169,6 +1217,7 @@ async fn toggle_workspace_error_handler(
|
||||
|
||||
async fn get_tokened_raw_script_by_path(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, token, path)): Path<(String, String, StripPath)>,
|
||||
Extension(cache): Extension<Arc<AuthCache>>,
|
||||
) -> Result<String> {
|
||||
@@ -1176,7 +1225,13 @@ async fn get_tokened_raw_script_by_path(
|
||||
.get_authed(Some(w_id.clone()), &token)
|
||||
.await
|
||||
.ok_or_else(|| Error::NotAuthorized("Invalid token".to_string()))?;
|
||||
return raw_script_by_path(authed, Extension(user_db), Path((w_id, path))).await;
|
||||
return raw_script_by_path(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
Extension(db),
|
||||
Path((w_id, path)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn get_empty_ts_script_by_path() -> String {
|
||||
@@ -1186,22 +1241,25 @@ async fn get_empty_ts_script_by_path() -> String {
|
||||
async fn raw_script_by_path(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> Result<String> {
|
||||
raw_script_by_path_internal(path, user_db, authed, w_id, false).await
|
||||
raw_script_by_path_internal(path, user_db, db, authed, w_id, false).await
|
||||
}
|
||||
|
||||
async fn raw_script_by_path_unpinned(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> Result<String> {
|
||||
raw_script_by_path_internal(path, user_db, authed, w_id, true).await
|
||||
raw_script_by_path_internal(path, user_db, db, authed, w_id, true).await
|
||||
}
|
||||
|
||||
async fn raw_script_by_path_internal(
|
||||
path: StripPath,
|
||||
user_db: UserDB,
|
||||
db: DB,
|
||||
authed: ApiAuthed,
|
||||
w_id: String,
|
||||
unpin: bool,
|
||||
@@ -1227,10 +1285,7 @@ async fn raw_script_by_path_internal(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let content_o = sqlx::query_scalar!(
|
||||
"SELECT content FROM script WHERE path = $1 AND workspace_id = $2 \
|
||||
AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND \
|
||||
workspace_id = $2)",
|
||||
"SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
@@ -1238,6 +1293,22 @@ async fn raw_script_by_path_internal(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
if content_o.is_none() {
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
if exists.unwrap_or(false) {
|
||||
return Err(Error::NotFound(format!(
|
||||
"Script {path} not visible to {} but exists",
|
||||
authed.username
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let content = not_found_if_none(content_o, "Script", path)?;
|
||||
|
||||
if unpin {
|
||||
@@ -1254,8 +1325,7 @@ async fn exists_script_by_path(
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))",
|
||||
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
@@ -1372,9 +1442,7 @@ pub async fn require_is_writer(authed: &ApiAuthed, path: &str, w_id: &str, db: D
|
||||
path,
|
||||
w_id,
|
||||
db,
|
||||
"SELECT extra_perms FROM script WHERE path = $1 AND workspace_id = $2 \
|
||||
AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
|
||||
workspace_id = $2)",
|
||||
"SELECT extra_perms FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
"script",
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::db::DB;
|
||||
use axum::Router;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use windmill_common::auth::aws::AwsAuthResourceType;
|
||||
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -14,6 +15,7 @@ pub fn start_sqs(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct SqsTrigger {
|
||||
pub queue_url: String,
|
||||
pub aws_auth_resource_type: AwsAuthResourceType,
|
||||
pub aws_resource_path: String,
|
||||
pub message_attributes: Option<Vec<String>>,
|
||||
pub path: String,
|
||||
|
||||
@@ -33,6 +33,8 @@ impl<B> OnResponse<B> for MyOnResponse {
|
||||
let status = response.status().as_u16();
|
||||
if response.status().is_success() || response.status().is_redirection() {
|
||||
tracing::info!(latency = latency, status = status, "response")
|
||||
} else if response.status().as_u16() == 404 {
|
||||
tracing::warn!(latency = latency, status = status, "response")
|
||||
} else {
|
||||
tracing::error!(latency = latency, status = status, "response")
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ pub(crate) async fn tarball_workspace(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by from app, app_version
|
||||
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)]",
|
||||
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND app_version.raw_app IS false",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -577,13 +577,35 @@ pub(crate) async fn tarball_workspace(
|
||||
#[cfg(feature = "websocket")]
|
||||
{
|
||||
let websocket_triggers = sqlx::query_as!(
|
||||
crate::websocket_triggers::WebsocketTrigger,
|
||||
"SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger
|
||||
WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
crate::websocket_triggers::WebsocketTrigger,
|
||||
r#"
|
||||
SELECT
|
||||
workspace_id,
|
||||
path,
|
||||
url,
|
||||
script_path,
|
||||
is_flow,
|
||||
edited_by,
|
||||
email,
|
||||
edited_at,
|
||||
server_id,
|
||||
last_server_ping,
|
||||
extra_perms,
|
||||
error,
|
||||
enabled,
|
||||
filters AS "filters: _",
|
||||
initial_messages AS "initial_messages: _",
|
||||
url_runnable_args AS "url_runnable_args: _",
|
||||
can_return_message
|
||||
FROM
|
||||
websocket_trigger
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
"#,
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
for trigger in websocket_triggers {
|
||||
let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap();
|
||||
@@ -622,8 +644,29 @@ pub(crate) async fn tarball_workspace(
|
||||
{
|
||||
let sqs_triggers = sqlx::query_as!(
|
||||
crate::sqs_triggers_ee::SqsTrigger,
|
||||
"SELECT * FROM sqs_trigger
|
||||
WHERE workspace_id = $1",
|
||||
r#"
|
||||
SELECT
|
||||
aws_auth_resource_type AS "aws_auth_resource_type: _",
|
||||
aws_resource_path,
|
||||
message_attributes,
|
||||
queue_url,
|
||||
workspace_id,
|
||||
path,
|
||||
script_path,
|
||||
is_flow,
|
||||
edited_by,
|
||||
email,
|
||||
edited_at,
|
||||
server_id,
|
||||
last_server_ping,
|
||||
extra_perms,
|
||||
error,
|
||||
enabled
|
||||
FROM
|
||||
sqs_trigger
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
"#,
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -649,6 +692,7 @@ pub(crate) async fn tarball_workspace(
|
||||
workspace_id,
|
||||
delivery_type AS "delivery_type: _",
|
||||
delivery_config AS "delivery_config: _",
|
||||
subscription_mode AS "subscription_mode: _",
|
||||
path,
|
||||
script_path,
|
||||
is_flow,
|
||||
|
||||
@@ -13,6 +13,7 @@ prometheus = ["dep:prometheus"]
|
||||
loki = ["dep:tracing-loki"]
|
||||
benchmark = []
|
||||
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"]
|
||||
aws_auth = ["dep:aws-sdk-sts", "dep:aws-config"]
|
||||
otel = ["dep:opentelemetry-semantic-conventions", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk",
|
||||
"dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-appender-tracing", "dep:tonic"]
|
||||
smtp = ["dep:mail-send"]
|
||||
@@ -75,6 +76,8 @@ quick_cache.workspace = true
|
||||
pin-project-lite.workspace = true
|
||||
futures.workspace = true
|
||||
tempfile.workspace = true
|
||||
systemstat.workspace = true
|
||||
size.workspace = true
|
||||
|
||||
opentelemetry-semantic-conventions = { workspace = true, optional = true }
|
||||
opentelemetry-otlp = { workspace = true, optional = true }
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Id in the `app_script` table.
|
||||
@@ -21,3 +23,8 @@ pub struct ListAppQuery {
|
||||
pub include_draft_only: Option<bool>,
|
||||
pub with_deployment_msg: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RawAppValue {
|
||||
pub files: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -10,6 +11,48 @@ use crate::{
|
||||
DB,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdToken {
|
||||
token: String,
|
||||
expiration: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub fn has_expired(expiration_time: DateTime<Utc>, take: Option<Duration>) -> bool {
|
||||
let now = Utc::now();
|
||||
|
||||
let expiration = match take {
|
||||
Some(duration) => expiration_time - duration,
|
||||
None => expiration_time,
|
||||
};
|
||||
|
||||
now > expiration
|
||||
}
|
||||
|
||||
impl From<IdToken> for String {
|
||||
fn from(value: IdToken) -> Self {
|
||||
value.token
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for IdToken {
|
||||
fn to_string(&self) -> String {
|
||||
self.token.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl IdToken {
|
||||
pub fn new(token: String, expiration: DateTime<Utc>) -> Self {
|
||||
Self { token, expiration }
|
||||
}
|
||||
|
||||
pub fn token(&self) -> &str {
|
||||
&self.token
|
||||
}
|
||||
pub fn expiration(&self) -> &DateTime<Utc> {
|
||||
&self.expiration
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct JWTAuthClaims {
|
||||
pub email: String,
|
||||
@@ -267,3 +310,110 @@ pub async fn create_token_for_owner(
|
||||
|
||||
Ok(format!("jwt_{}", token))
|
||||
}
|
||||
|
||||
#[cfg(feature = "aws_auth")]
|
||||
pub mod aws {
|
||||
|
||||
use crate::error::to_anyhow;
|
||||
|
||||
use super::*;
|
||||
use crate::utils::empty_string_as_none;
|
||||
use aws_config::{BehaviorVersion, Region};
|
||||
use aws_sdk_sts::{
|
||||
config::Credentials as AwsCredentials,
|
||||
operation::{
|
||||
assume_role_with_saml::AssumeRoleWithSamlOutput,
|
||||
assume_role_with_web_identity::AssumeRoleWithWebIdentityOutput,
|
||||
},
|
||||
types::Credentials,
|
||||
Client,
|
||||
};
|
||||
|
||||
pub const AWS_OIDC_AUDIENCE: &'static str = "sts.amazonaws.com";
|
||||
|
||||
pub trait GetAuthenticationOutput {
|
||||
fn get_credentials(&self) -> Result<&Credentials>;
|
||||
}
|
||||
|
||||
impl GetAuthenticationOutput for AssumeRoleWithSamlOutput {
|
||||
fn get_credentials(&self) -> Result<&Credentials> {
|
||||
let credentials = self.credentials.as_ref().ok_or(Error::BadGateway(
|
||||
"Error fetching credentials from AWS STS".to_string(),
|
||||
))?;
|
||||
Ok(credentials)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAuthenticationOutput for AssumeRoleWithWebIdentityOutput {
|
||||
fn get_credentials(&self) -> Result<&Credentials> {
|
||||
let credentials = self.credentials.as_ref().ok_or(Error::BadGateway(
|
||||
"Error fetching credentials from AWS STS".to_string(),
|
||||
))?;
|
||||
Ok(credentials)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "AWS_AUTH_RESOURCE_TYPE", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AwsAuthResourceType {
|
||||
Credentials,
|
||||
Oidc,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CredentialsAuth {
|
||||
#[serde(deserialize_with = "empty_string_as_none")]
|
||||
pub region: Option<String>,
|
||||
#[serde(rename = "awsAccessKeyId")]
|
||||
pub aws_access_key_id: String,
|
||||
#[serde(rename = "awsSecretAccessKey")]
|
||||
pub aws_secret_access_key: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct OidcAuth {
|
||||
#[serde(deserialize_with = "empty_string_as_none")]
|
||||
pub region: Option<String>,
|
||||
#[serde(rename = "roleArn")]
|
||||
pub role_arn: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum AWSAuthConfig {
|
||||
Credentials(CredentialsAuth),
|
||||
Oidc(OidcAuth),
|
||||
}
|
||||
|
||||
pub async fn get_oidc_authentication_data(
|
||||
oidc_auth: OidcAuth,
|
||||
role_session_name: Option<impl ToString>,
|
||||
token: String,
|
||||
) -> Result<AssumeRoleWithWebIdentityOutput> {
|
||||
let region = oidc_auth.region.unwrap_or_else(|| "us-east-1".to_string());
|
||||
|
||||
let credentials = AwsCredentials::new("", "", None, None, "UserInput");
|
||||
|
||||
let config = aws_config::defaults(BehaviorVersion::latest())
|
||||
.credentials_provider(credentials)
|
||||
.region(Region::new(region.clone()))
|
||||
.load()
|
||||
.await;
|
||||
|
||||
let assume_role_with_web_identity_fluent_builder = Client::new(&config)
|
||||
.assume_role_with_web_identity()
|
||||
.set_role_arn(Some(oidc_auth.role_arn))
|
||||
.set_role_session_name(role_session_name.map(|str| str.to_string()))
|
||||
.set_web_identity_token(Some(token));
|
||||
|
||||
let resp = assume_role_with_web_identity_fluent_builder
|
||||
.clone()
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,15 +570,18 @@ pub mod script {
|
||||
let fut = CACHE.get_or_insert_async(hash, async move {
|
||||
match conn {
|
||||
Connection::Sql(db) => fetch_script_from_db(&db, hash, loc).await,
|
||||
Connection::Http(_) => Err(error::Error::InternalErr(format!(
|
||||
"Cannot fetch script in HTTP mode"
|
||||
))),
|
||||
Connection::Http(client) => {
|
||||
let r = client
|
||||
.get::<RawScriptApi>(&format!("/api/agent_workers/script/{}", hash.0))
|
||||
.await?;
|
||||
Ok(r.into())
|
||||
}
|
||||
}
|
||||
});
|
||||
fut.map_ok(|ScriptFull { data, meta }| (data, meta))
|
||||
}
|
||||
|
||||
async fn fetch_script_from_db(
|
||||
pub async fn fetch_script_from_db(
|
||||
db: &DB,
|
||||
hash: ScriptHash,
|
||||
loc: &'static Location<'_>,
|
||||
|
||||
@@ -98,3 +98,9 @@ pub async fn worker_groups_alerts(_db: &DB) {}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn jobs_waiting_alerts(_db: &DB) {}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn low_disk_alerts(_db: &DB, _server_mode: bool, _worker_mode: bool, _workers: Vec<String>) {
|
||||
// Implementation is not open source
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ use crate::{
|
||||
error::Error,
|
||||
more_serde::{default_empty_string, default_id, default_null, default_true, is_default},
|
||||
scripts::{Schema, ScriptHash, ScriptLang},
|
||||
worker::{to_raw_value, Connection}, DB,
|
||||
worker::{to_raw_value, Connection},
|
||||
DB,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, sqlx::FromRow)]
|
||||
|
||||
@@ -37,6 +37,7 @@ pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
|
||||
pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url";
|
||||
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
|
||||
pub const CRITICAL_ALERT_MUTE_UI_SETTING: &str = "critical_alert_mute_ui";
|
||||
pub const CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING: &str = "critical_alerts_on_db_oversize";
|
||||
pub const DEV_INSTANCE_SETTING: &str = "dev_instance";
|
||||
pub const JWT_SECRET_SETTING: &str = "jwt_secret";
|
||||
pub const EMAIL_DOMAIN_SETTING: &str = "email_domain";
|
||||
|
||||
@@ -115,6 +115,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
|
||||
pub static ref CRITICAL_ERROR_CHANNELS: Arc<RwLock<Vec<CriticalErrorChannel>>> = Arc::new(RwLock::new(vec![]));
|
||||
pub static ref CRITICAL_ALERTS_ON_DB_OVERSIZE: Arc<RwLock<Option<f32>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
pub static ref JOB_RETENTION_SECS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));
|
||||
|
||||
|
||||
@@ -476,3 +476,7 @@ impl CredentialProvider for AwsCredentialAdapter {
|
||||
pub fn bundle(w_id: &str, hash: &str) -> String {
|
||||
format!("script_bundle/{}/{}", w_id, hash)
|
||||
}
|
||||
|
||||
pub fn raw_app(w_id: &str, version: &i64) -> String {
|
||||
format!("/home/rfiszel/raw_app/{}/{}", w_id, version)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user