Compare commits
261 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39f0752525 | ||
|
|
1ee78da4bf | ||
|
|
451b6bb22d | ||
|
|
0a336467d8 | ||
|
|
250237793b | ||
|
|
632bd18a2e | ||
|
|
de4f5e3015 | ||
|
|
99f7828ebb | ||
|
|
5a8fa1d724 | ||
|
|
49835ca6ca | ||
|
|
43459ce03a | ||
|
|
e23c3fad62 | ||
|
|
dffd5f7f7d | ||
|
|
55340029db | ||
|
|
c9ef2c8e97 | ||
|
|
3cf4f00dca | ||
|
|
2afdb66e75 | ||
|
|
6705b6706f | ||
|
|
c4282a0656 | ||
|
|
6c9e32af10 | ||
|
|
5416254aa1 | ||
|
|
11b81e3d9d | ||
|
|
0cce27636d | ||
|
|
e0857c7178 | ||
|
|
0eb96ded5f | ||
|
|
be90b3e219 | ||
|
|
fbd9b87c0f | ||
|
|
75d96baf50 | ||
|
|
58adf6ad2d | ||
|
|
ad698768e8 | ||
|
|
ecea507670 | ||
|
|
acfe6325a1 | ||
|
|
3bd461ffa1 | ||
|
|
c09f078928 | ||
|
|
f88efc2380 | ||
|
|
2aa40c413e | ||
|
|
9363e6d05f | ||
|
|
0be8982ddc | ||
|
|
969699ce47 | ||
|
|
b9924d01d5 | ||
|
|
35fed96cd3 | ||
|
|
bcc94badbd | ||
|
|
2021b8f86a | ||
|
|
b626e52324 | ||
|
|
7a68cc76c2 | ||
|
|
b424c61e95 | ||
|
|
41bf933fb7 | ||
|
|
f00545f169 | ||
|
|
95a898a5d5 | ||
|
|
1d0807f542 | ||
|
|
527183874c | ||
|
|
0d5c2e5dd7 | ||
|
|
599399870f | ||
|
|
955a980703 | ||
|
|
7eccebf5da | ||
|
|
1719c2689e | ||
|
|
2b99789077 | ||
|
|
fd1c456fad | ||
|
|
3c4b8377ed | ||
|
|
36129fb470 | ||
|
|
e779f96386 | ||
|
|
df33b281e8 | ||
|
|
6ba3fda65d | ||
|
|
0655803860 | ||
|
|
c288d73c13 | ||
|
|
c03c0f6172 | ||
|
|
bc512bd307 | ||
|
|
e5e03b5baa | ||
|
|
02fdd58e47 | ||
|
|
6272c9ff41 | ||
|
|
7115a35779 | ||
|
|
c0853eafc9 | ||
|
|
d56a956b9a | ||
|
|
7349d0a320 | ||
|
|
70d1e6c720 | ||
|
|
b78c3fe574 | ||
|
|
71d23db5cb | ||
|
|
832df264b4 | ||
|
|
2334802384 | ||
|
|
8ea2906d09 | ||
|
|
88d0e97ce0 | ||
|
|
d1e119e052 | ||
|
|
bf629f39cc | ||
|
|
c90fb8d293 | ||
|
|
55926f957f | ||
|
|
2eb6a7a742 | ||
|
|
a77f74cb7e | ||
|
|
413ad2c925 | ||
|
|
2c3d4920e0 | ||
|
|
0029dc11e9 | ||
|
|
dd17a6bd14 | ||
|
|
4f9193c036 | ||
|
|
843b14fe99 | ||
|
|
b60f42e387 | ||
|
|
c01e179d8f | ||
|
|
15d5c60d90 | ||
|
|
d88674a7af | ||
|
|
6e9c350b4a | ||
|
|
e7148f6723 | ||
|
|
a84c54d663 | ||
|
|
cfa20ae0d7 | ||
|
|
b54edf153e | ||
|
|
5e3a3e2103 | ||
|
|
a87f34fb4a | ||
|
|
7886f8f471 | ||
|
|
1e7de238cf | ||
|
|
9e78a51adc | ||
|
|
b9b30e66ec | ||
|
|
6749f2c136 | ||
|
|
39dc6857eb | ||
|
|
b6ab184889 | ||
|
|
5104dba631 | ||
|
|
a1c40d7fd0 | ||
|
|
7b3128171e | ||
|
|
657f03bc67 | ||
|
|
fbcac1e0a6 | ||
|
|
1c83876775 | ||
|
|
adecf4b2d1 | ||
|
|
f2f8bbe1d4 | ||
|
|
f1fa6bca84 | ||
|
|
704e75e893 | ||
|
|
d771e38bfd | ||
|
|
cc111ba7dc | ||
|
|
43a89ee1cc | ||
|
|
947dd21933 | ||
|
|
989c9e6102 | ||
|
|
811de58712 | ||
|
|
3508b6d793 | ||
|
|
86e23f2aed | ||
|
|
a168107d29 | ||
|
|
63abd5b146 | ||
|
|
c55d15c0a9 | ||
|
|
91e364bde5 | ||
|
|
1723e989cd | ||
|
|
1a25aca45c | ||
|
|
cd61fc1e8c | ||
|
|
86a6a909d9 | ||
|
|
6cd698eeda | ||
|
|
edcee6d861 | ||
|
|
e6d5689bf9 | ||
|
|
efcf0e40f6 | ||
|
|
42e3ae92e3 | ||
|
|
b5464e2906 | ||
|
|
2c27083e2a | ||
|
|
2b6f84e5d8 | ||
|
|
c6b07561da | ||
|
|
1a47325055 | ||
|
|
dd1a7a1b3d | ||
|
|
79851101ae | ||
|
|
6808ee5a70 | ||
|
|
efeb65be4c | ||
|
|
10c699759e | ||
|
|
d69aa8d484 | ||
|
|
b95337fcb9 | ||
|
|
9921731fdf | ||
|
|
e7cccd099e | ||
|
|
be8cedfe7f | ||
|
|
1a548f32a1 | ||
|
|
72ff465300 | ||
|
|
716bb7118b | ||
|
|
4fe797eeab | ||
|
|
459a51c111 | ||
|
|
8c81e47564 | ||
|
|
f59046a924 | ||
|
|
e5c73d66d3 | ||
|
|
cf3512ee03 | ||
|
|
28277da35b | ||
|
|
f63b176328 | ||
|
|
ccb892feab | ||
|
|
5992b82180 | ||
|
|
6a33624416 | ||
|
|
06433a6ec6 | ||
|
|
e02e644d4c | ||
|
|
9942e86765 | ||
|
|
9f8862680c | ||
|
|
6e91005daa | ||
|
|
5031a8cb01 | ||
|
|
28810910b1 | ||
|
|
71badb959d | ||
|
|
bc94acb378 | ||
|
|
ca345d4755 | ||
|
|
b250890b1f | ||
|
|
18f22be2ba | ||
|
|
b02baa2cfb | ||
|
|
fb89eed8fa | ||
|
|
eb6557a6be | ||
|
|
4f114cbcab | ||
|
|
2cc24b675e | ||
|
|
40caa795fb | ||
|
|
e45a8a4591 | ||
|
|
3f1ed35f3e | ||
|
|
9deba3e715 | ||
|
|
1e2e907982 | ||
|
|
c3848e2e30 | ||
|
|
35d567e2e0 | ||
|
|
7099ec48e6 | ||
|
|
df8cf402c6 | ||
|
|
2b67fbcd98 | ||
|
|
ec9aad4dae | ||
|
|
99623f31af | ||
|
|
0e908ec71f | ||
|
|
c52ca177ea | ||
|
|
545a57fd2c | ||
|
|
f1d5be8a2a | ||
|
|
6167fbb9a5 | ||
|
|
07c764c167 | ||
|
|
78fd99cb68 | ||
|
|
54e4397a0b | ||
|
|
c270ab1171 | ||
|
|
12f9e56154 | ||
|
|
6df9eca2cf | ||
|
|
38f0949ce3 | ||
|
|
23108b4688 | ||
|
|
8daccfc876 | ||
|
|
d87ae08362 | ||
|
|
7102dd81c0 | ||
|
|
8e615c900d | ||
|
|
9300ee6f47 | ||
|
|
01f2eeb3fe | ||
|
|
92dac027f2 | ||
|
|
9c63623436 | ||
|
|
289766281c | ||
|
|
9fd3ec5b03 | ||
|
|
80d4fb0352 | ||
|
|
aaf067eedb | ||
|
|
204e2fafda | ||
|
|
d1012ea930 | ||
|
|
7fbd0028c1 | ||
|
|
eead10bb80 | ||
|
|
d49b2d0f06 | ||
|
|
83d3f491b3 | ||
|
|
6d1fd24d37 | ||
|
|
8fcda68af0 | ||
|
|
20390c53e8 | ||
|
|
c0f3a40c07 | ||
|
|
7047e2975d | ||
|
|
5355af427d | ||
|
|
eceb472d16 | ||
|
|
cac39a1051 | ||
|
|
14a4f12826 | ||
|
|
97f6b2e7ad | ||
|
|
c6dc06bf70 | ||
|
|
9ab5b2e32d | ||
|
|
13e9e8de9e | ||
|
|
73decb24b2 | ||
|
|
d096704b27 | ||
|
|
48e9f089be | ||
|
|
99509ac683 | ||
|
|
ae4cbb0401 | ||
|
|
05388053e8 | ||
|
|
a117a4d017 | ||
|
|
bb861cfede | ||
|
|
0f91539977 | ||
|
|
ab686ea69e | ||
|
|
e7cdc2b934 | ||
|
|
7d4dc8df26 | ||
|
|
6e33e4e0b9 | ||
|
|
8a89652255 | ||
|
|
f4c9ba00f1 | ||
|
|
25d230d8d8 | ||
|
|
f6a948ba77 |
2
.github/DockerfileBackendTests
vendored
2
.github/DockerfileBackendTests
vendored
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.78-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.79-slim-bookworm
|
||||
ARG PYTHON_IMAGE=python:3.11.4-slim-bookworm
|
||||
|
||||
FROM ${DEBIAN_IMAGE} as downloader
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 1.78.0
|
||||
toolchain: 1.79.0
|
||||
# - uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# workspaces: |
|
||||
|
||||
45
.github/workflows/build-caddy-l4-image.yml
vendored
Normal file
45
.github/workflows/build-caddy-l4-image.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/caddy-l4
|
||||
|
||||
name: Build caddy-l4
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build_ee:
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: depot/setup-action@v1
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha
|
||||
type=ref,event=branch
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: ./docker
|
||||
file: ./docker/DockerfileCaddyL4
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
76
.github/workflows/docker-image-rpi4.yml
vendored
Normal file
76
.github/workflows/docker-image-rpi4.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}-rpi
|
||||
|
||||
name: Build windmill without jemalloc
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: windmill-without-jemalloc
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: windmill-labs/windmill-ee-private
|
||||
path: ./windmill-ee-private
|
||||
ref: ${{ env.ee_repo_ref }}
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=AGPLv3
|
||||
52
.github/workflows/docker-image.yml
vendored
52
.github/workflows/docker-image.yml
vendored
@@ -1,6 +1,7 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.repository || 'windmill-labs/windmill-test' }}
|
||||
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}', github.event.number) }}
|
||||
|
||||
name: Build windmill:main
|
||||
on:
|
||||
@@ -62,7 +63,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-public.outputs.labels }}
|
||||
@@ -113,7 +113,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -137,7 +136,7 @@ jobs:
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
@@ -176,7 +175,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -250,25 +248,25 @@ jobs:
|
||||
|
||||
- run: |
|
||||
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
- run: |
|
||||
# Checks the image is in docker prior to running actions-docker-extract. It fails if not
|
||||
# Also useful to visually check that the arch is the right opencontainers
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
- uses: shrink/actions-docker-extract@v3
|
||||
id: extract
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
path: "/usr/src/app/windmill"
|
||||
|
||||
- uses: shrink/actions-docker-extract@v3
|
||||
id: extract-ee
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
path: "/usr/src/app/windmill"
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
@@ -294,25 +292,25 @@ jobs:
|
||||
|
||||
# - run: |
|
||||
# # pulling docker image with desired arch so that actions-docker-extract doesn't do it
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
# - run: |
|
||||
# # Checks the image is in docker prior to running actions-docker-extract. It fails if not
|
||||
# # Also useful to visually check that the arch is the right opencontainers
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
# - uses: shrink/actions-docker-extract@v3
|
||||
# id: extract
|
||||
# with:
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
# path: "/usr/src/app/windmill"
|
||||
|
||||
# - uses: shrink/actions-docker-extract@v3
|
||||
# id: extract-ee
|
||||
# with:
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
# path: "/usr/src/app/windmill"
|
||||
|
||||
# - name: Rename binary with corresponding architecture
|
||||
@@ -354,6 +352,7 @@ jobs:
|
||||
tag_latest:
|
||||
runs-on: ubicloud
|
||||
needs: [run_integration_test, build]
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -366,12 +365,13 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Tag main and latest
|
||||
run: |
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
|
||||
|
||||
tag_latest_ee:
|
||||
runs-on: ubicloud
|
||||
needs: [run_integration_test, build_ee]
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -384,8 +384,8 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Tag main and latest for ee
|
||||
run: |
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
|
||||
|
||||
verify_ee_image_vulnerabilities:
|
||||
runs-on: ubicloud
|
||||
@@ -432,6 +432,7 @@ jobs:
|
||||
build_ee_nsjail:
|
||||
needs: [build_ee]
|
||||
runs-on: ubicloud
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -451,8 +452,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
|
||||
@@ -480,6 +479,7 @@ jobs:
|
||||
build_ee_reports_privately:
|
||||
needs: [build_ee_nsjail]
|
||||
runs-on: ubicloud
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -503,8 +503,6 @@ jobs:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-reports
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
|
||||
@@ -589,7 +587,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -636,7 +633,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
|
||||
389
CHANGELOG.md
389
CHANGELOG.md
@@ -1,5 +1,394 @@
|
||||
# Changelog
|
||||
|
||||
## [1.381.0](https://github.com/windmill-labs/windmill/compare/v1.380.0...v1.381.0) (2024-08-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add env to refresh cgroup readings ([#4250](https://github.com/windmill-labs/windmill/issues/4250)) ([e23c3fa](https://github.com/windmill-labs/windmill/commit/e23c3fad628cc718314472f3cb53c0f257c4c9e5))
|
||||
* cache common hub scripts in image ([#4249](https://github.com/windmill-labs/windmill/issues/4249)) ([99f7828](https://github.com/windmill-labs/windmill/commit/99f7828ebb5fddf799afb52af7214ba4119e57b9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** add inject and define options ([dffd5f7](https://github.com/windmill-labs/windmill/commit/dffd5f7f7d5c84624953bf8c778b14586a14e2d4))
|
||||
* **frontend:** improve UI for email triggers ([#4243](https://github.com/windmill-labs/windmill/issues/4243)) ([6c9e32a](https://github.com/windmill-labs/windmill/commit/6c9e32af104a897e5c2a5b41867d2ffbbacc8acb))
|
||||
* improve password field lifetime incorrectly recycled too early ([5a8fa1d](https://github.com/windmill-labs/windmill/commit/5a8fa1d72487ac2a29dca8833b8c92b8cac3726e))
|
||||
* improve row update of aggrid table actions II ([3cf4f00](https://github.com/windmill-labs/windmill/commit/3cf4f00dca677fc592d5dde8a7b8fdcac5f08e0a))
|
||||
* **typescript-client:** add runFlow and runFlowAsync ([c9ef2c8](https://github.com/windmill-labs/windmill/commit/c9ef2c8e97bdecb16b9a54805f1c90523e3b406f))
|
||||
* workspace specific default tags do not override step level custom tags ([49835ca](https://github.com/windmill-labs/windmill/commit/49835ca6ca65e569ea2915b2ef4f7ce1c4988cae))
|
||||
|
||||
## [1.380.0](https://github.com/windmill-labs/windmill/compare/v1.379.4...v1.380.0) (2024-08-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* opt-in job args in audit logs ([#4241](https://github.com/windmill-labs/windmill/issues/4241)) ([0cce276](https://github.com/windmill-labs/windmill/commit/0cce27636d128972730427a33b7be954d00210dc))
|
||||
* recoverable error handlers ([e0857c7](https://github.com/windmill-labs/windmill/commit/e0857c7178ac9657173e9ed597d072130e41fe47))
|
||||
* togglable continue on disapproval/timeout of approvals ([be90b3e](https://github.com/windmill-labs/windmill/commit/be90b3e2192cdae88e38fd7e269ad8abc8f7c054))
|
||||
|
||||
## [1.379.4](https://github.com/windmill-labs/windmill/compare/v1.379.3...v1.379.4) (2024-08-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing change for better key renewal ([#4237](https://github.com/windmill-labs/windmill/issues/4237)) ([f88efc2](https://github.com/windmill-labs/windmill/commit/f88efc2380bf5b055b2633b24cb94fc46e6cdca5))
|
||||
* fix transformer issue after proxy change for apps ([ad69876](https://github.com/windmill-labs/windmill/commit/ad698768e8fdbcc4a90f138f74ceee20d2d68a9b))
|
||||
* handle time with tz col type in pg ([#4239](https://github.com/windmill-labs/windmill/issues/4239)) ([c09f078](https://github.com/windmill-labs/windmill/commit/c09f078928fb9f4b52df641580505d98bb728aa0))
|
||||
* use job timeout for snowflake timeout ([#4240](https://github.com/windmill-labs/windmill/issues/4240)) ([3bd461f](https://github.com/windmill-labs/windmill/commit/3bd461ffa1fcbe19c9007b1b15970be21cff08ef))
|
||||
|
||||
## [1.379.3](https://github.com/windmill-labs/windmill/compare/v1.379.2...v1.379.3) (2024-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve scrolling performance of the runs page ([0be8982](https://github.com/windmill-labs/windmill/commit/0be8982ddc263b76dab2c87cb6fa63e21effd58c))
|
||||
|
||||
## [1.379.2](https://github.com/windmill-labs/windmill/compare/v1.379.1...v1.379.2) (2024-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add fetch connection limits to bun type fetcher ([b9924d0](https://github.com/windmill-labs/windmill/commit/b9924d01d54f6dfaf5d719c13a38bb3dccbfe3b9))
|
||||
* extend step_id being returned as part of the error of every languages ([bcc94ba](https://github.com/windmill-labs/windmill/commit/bcc94badbd848b6838dd0e353c3a30e7a1889ff8))
|
||||
* **frontend:** date input is more flexible and accept default html format as a fallback ([b424c61](https://github.com/windmill-labs/windmill/commit/b424c61e95e3cc9213d4d3efbe4173857d4e80fd))
|
||||
* improve logviewer behavior when job is loading ([7a68cc7](https://github.com/windmill-labs/windmill/commit/7a68cc76c2d5d7a66f080afdfcb6c5cd09fc25b5))
|
||||
|
||||
## [1.379.1](https://github.com/windmill-labs/windmill/compare/v1.379.0...v1.379.1) (2024-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add an option to disable bundling globally ([f00545f](https://github.com/windmill-labs/windmill/commit/f00545f1691c5107d1103f89bf68fdc3915ddd82))
|
||||
* **apps:** improve tanstack table handling of objects ([1d0807f](https://github.com/windmill-labs/windmill/commit/1d0807f54238c8d8d242e3e5534c6d0868b3172b))
|
||||
|
||||
## [1.379.0](https://github.com/windmill-labs/windmill/compare/v1.378.0...v1.379.0) (2024-08-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* embeddable apps using jwt ([#4229](https://github.com/windmill-labs/windmill/issues/4229)) ([fd1c456](https://github.com/windmill-labs/windmill/commit/fd1c456fad16229112d9329e7c7eb9d60561efc0))
|
||||
* **frontend:** group fields are mutable ([955a980](https://github.com/windmill-labs/windmill/commit/955a980703362b78bf24a47ec348878d7aac94e3))
|
||||
* **frontend:** improve display of waiting jobs + schedule filter + suspended jobs on runs page ([2b99789](https://github.com/windmill-labs/windmill/commit/2b99789077b66991902819b26b140e2681a51344))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apps:** type hints for results are automatically widened ([1719c26](https://github.com/windmill-labs/windmill/commit/1719c2689e2cdcbf4e88a5133e0d6c5626c8d6d9))
|
||||
* support NODE_PATH ([3c4b837](https://github.com/windmill-labs/windmill/commit/3c4b8377ed4689704cb7baaee13e8acaa91e8611))
|
||||
|
||||
## [1.378.0](https://github.com/windmill-labs/windmill/compare/v1.377.1...v1.378.0) (2024-08-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** add customerBundler support ([70d1e6c](https://github.com/windmill-labs/windmill/commit/70d1e6c7208b59890ad4d15cad28b1f32d7970d2))
|
||||
* windmill embed ([d56a956](https://github.com/windmill-labs/windmill/commit/d56a956b9a471cca4042f43ba4e3922e42f121f1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow user resources in app to work within iframes ([6272c9f](https://github.com/windmill-labs/windmill/commit/6272c9ff41e5359f38ae13e879aa615da0fb0d09))
|
||||
* **frontend:** improve default id of the components of the topbar ([#4222](https://github.com/windmill-labs/windmill/issues/4222)) ([7115a35](https://github.com/windmill-labs/windmill/commit/7115a3577947c52aa72edcc134dcb8e739208df0))
|
||||
* **frontend:** support plus sign in emails format fields [#4223](https://github.com/windmill-labs/windmill/issues/4223) ([c0853ea](https://github.com/windmill-labs/windmill/commit/c0853eafc9f9bf6d454c6a70800c98c75c9d8ba3))
|
||||
* **frontend:** user resource picker for app use lightweight component ([c03c0f6](https://github.com/windmill-labs/windmill/commit/c03c0f61726e7b6196fe5222c44d4d635e907b20))
|
||||
* improve default value handling for date & date-time in apps ([e779f96](https://github.com/windmill-labs/windmill/commit/e779f963869fcb22d73f02b6a806e3fdac978959))
|
||||
* improve license key ui ([#4220](https://github.com/windmill-labs/windmill/issues/4220)) ([0655803](https://github.com/windmill-labs/windmill/commit/06558038601d6f78b234519d4b48f229a2bf9fc8))
|
||||
|
||||
## [1.377.1](https://github.com/windmill-labs/windmill/compare/v1.377.0...v1.377.1) (2024-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fix timezone issues for Date only inputs ([#4215](https://github.com/windmill-labs/windmill/issues/4215)) ([2334802](https://github.com/windmill-labs/windmill/commit/2334802384e86c4269e1cbfd4f6f98a9a1e58f68))
|
||||
|
||||
## [1.377.0](https://github.com/windmill-labs/windmill/compare/v1.376.1...v1.377.0) (2024-08-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **app:** add user resource select component ([413ad2c](https://github.com/windmill-labs/windmill/commit/413ad2c9259fd30871b5184bc4f7c3fece60918d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Additional tracing when pulling and uploading search index [#4207](https://github.com/windmill-labs/windmill/issues/4207) ([4f9193c](https://github.com/windmill-labs/windmill/commit/4f9193c03664c336095e9aa2ea6232a4815c7387))
|
||||
* azure git sync test connection ([#4209](https://github.com/windmill-labs/windmill/issues/4209)) ([2c3d492](https://github.com/windmill-labs/windmill/commit/2c3d4920e0b575019dd18d0a4a9609e6f66be60e))
|
||||
* case insensitive encoding for email triggers ([#4211](https://github.com/windmill-labs/windmill/issues/4211)) ([55926f9](https://github.com/windmill-labs/windmill/commit/55926f957f83cf87a52070577ed79bed15e38836))
|
||||
* **cli:** Add esbuild loader for .node files for codebase ([a77f74c](https://github.com/windmill-labs/windmill/commit/a77f74cb7ea5ed3633564075169a5aeedae06152))
|
||||
* **frontend:** fix decision tree debug menu ([#4212](https://github.com/windmill-labs/windmill/issues/4212)) ([c90fb8d](https://github.com/windmill-labs/windmill/commit/c90fb8d293ef3aaa8264c2a2984e4203e4851f5c))
|
||||
* **frontend:** fix flow warnings ([#4213](https://github.com/windmill-labs/windmill/issues/4213)) ([bf629f3](https://github.com/windmill-labs/windmill/commit/bf629f39ccc411b6ed98081f414a8c3238d52957))
|
||||
* handle >1 num workers stats ([#4177](https://github.com/windmill-labs/windmill/issues/4177)) ([0029dc1](https://github.com/windmill-labs/windmill/commit/0029dc11e9f0828c4a129586ac90a18b3f4dfe79))
|
||||
* handle snowflake partitions ([#4214](https://github.com/windmill-labs/windmill/issues/4214)) ([d1e119e](https://github.com/windmill-labs/windmill/commit/d1e119e0526933cf39320eecf7c4836cd45d0eb0))
|
||||
|
||||
## [1.376.1](https://github.com/windmill-labs/windmill/compare/v1.376.0...v1.376.1) (2024-08-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* email triggers sqlx CE and improve email parsing ([#4203](https://github.com/windmill-labs/windmill/issues/4203)) ([6e9c350](https://github.com/windmill-labs/windmill/commit/6e9c350b4aa27355d03cb409da36e8f44ff548dc))
|
||||
* mysql params starting with underscore ([#4201](https://github.com/windmill-labs/windmill/issues/4201)) ([e7148f6](https://github.com/windmill-labs/windmill/commit/e7148f672303ccf2b5c49bd9d75ee9764657490e))
|
||||
|
||||
## [1.376.0](https://github.com/windmill-labs/windmill/compare/v1.375.0...v1.376.0) (2024-08-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* email triggers ([#4163](https://github.com/windmill-labs/windmill/issues/4163)) ([a87f34f](https://github.com/windmill-labs/windmill/commit/a87f34fb4a5e54da38e2dbabf2ac551d35b021ee))
|
||||
* secure ctx variables in runnable inputs ([#4142](https://github.com/windmill-labs/windmill/issues/4142)) ([b54edf1](https://github.com/windmill-labs/windmill/commit/b54edf153ec90b9bafad9ed7bc92c29345951fe6))
|
||||
* Tag filter on Runs page ([#4193](https://github.com/windmill-labs/windmill/issues/4193)) ([7b31281](https://github.com/windmill-labs/windmill/commit/7b3128171ea7193efea569297a099bb9ee6935e1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix native scripts access to reserved variables ([7886f8f](https://github.com/windmill-labs/windmill/commit/7886f8f471bcb33f7cc640eab64ffc0dd3cb1726))
|
||||
* **frontend:** disable email triggers by default ([#4199](https://github.com/windmill-labs/windmill/issues/4199)) ([5e3a3e2](https://github.com/windmill-labs/windmill/commit/5e3a3e2103ce6218a60c570546355ef6ca574df2))
|
||||
* **frontend:** fix the app created from a script or flow with the new topbar ([#4194](https://github.com/windmill-labs/windmill/issues/4194)) ([657f03b](https://github.com/windmill-labs/windmill/commit/657f03bc67f84a8593b28483684c2ad8ad432865))
|
||||
* **frontend:** Fr/improve suspend drawer ([#4189](https://github.com/windmill-labs/windmill/issues/4189)) ([5104dba](https://github.com/windmill-labs/windmill/commit/5104dba63140e2186dc1eed7f0bf4e14e357bf3d))
|
||||
* **frontend:** Hide AgChart background to make styling work ([#4197](https://github.com/windmill-labs/windmill/issues/4197)) ([b9b30e6](https://github.com/windmill-labs/windmill/commit/b9b30e66ec485cc019561b092fc27d08aa4666b3))
|
||||
* **frontend:** Remove full height for the event handlers of runnables ([#4196](https://github.com/windmill-labs/windmill/issues/4196)) ([6749f2c](https://github.com/windmill-labs/windmill/commit/6749f2c1367bdbecb12941baaa87b63c4b4c6f20))
|
||||
* mysql support for underscore in named param ([#4200](https://github.com/windmill-labs/windmill/issues/4200)) ([cfa20ae](https://github.com/windmill-labs/windmill/commit/cfa20ae0d740014baea2e534cea5f787acaeb028))
|
||||
|
||||
## [1.375.0](https://github.com/windmill-labs/windmill/compare/v1.374.0...v1.375.0) (2024-08-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* deployment UI filter deployable items ([#4183](https://github.com/windmill-labs/windmill/issues/4183)) ([f2f8bbe](https://github.com/windmill-labs/windmill/commit/f2f8bbe1d4364540854fef3bf11df5bb4252c17d))
|
||||
* improve indices of completed_runs for faster load ([cc111ba](https://github.com/windmill-labs/windmill/commit/cc111ba7dcd7fe6c4280d394d4eb0e723e9d8b19))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** add missing truncate for branch predicates ([#4180](https://github.com/windmill-labs/windmill/issues/4180)) ([947dd21](https://github.com/windmill-labs/windmill/commit/947dd219335244728213e95a46e577428ac65464))
|
||||
* **frontend:** fix style panel for compoentn without custom css ([#4182](https://github.com/windmill-labs/windmill/issues/4182)) ([43a89ee](https://github.com/windmill-labs/windmill/commit/43a89ee1ccbeeac11211277f8005d85541c71dc3))
|
||||
* **frontend:** fix tutorial for apps with the new topbar ([#4186](https://github.com/windmill-labs/windmill/issues/4186)) ([704e75e](https://github.com/windmill-labs/windmill/commit/704e75e893a94e71091c840aa5608837c0617526))
|
||||
|
||||
## [1.374.0](https://github.com/windmill-labs/windmill/compare/v1.373.1...v1.374.0) (2024-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for assets using tar for codebase deploy ([3508b6d](https://github.com/windmill-labs/windmill/commit/3508b6d793b0ce21c0c264a50bb000cd61c21920))
|
||||
* caddy with l4 image ([#4178](https://github.com/windmill-labs/windmill/issues/4178)) ([811de58](https://github.com/windmill-labs/windmill/commit/811de58712f619045a3653fe5b98ff0c7599f392))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix delete job ([86e23f2](https://github.com/windmill-labs/windmill/commit/86e23f2aed234a64fce46e1b6253f788546876af))
|
||||
|
||||
## [1.373.1](https://github.com/windmill-labs/windmill/compare/v1.373.0...v1.373.1) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix run_flow_async from call ([63abd5b](https://github.com/windmill-labs/windmill/commit/63abd5b14606869eafbd25d8469638b85b96e199))
|
||||
* **frontend:** Add support for array of objects in th UI ([#4170](https://github.com/windmill-labs/windmill/issues/4170)) ([91e364b](https://github.com/windmill-labs/windmill/commit/91e364bde5459447945d7dae65c1cab2858c69ac))
|
||||
|
||||
## [1.373.0](https://github.com/windmill-labs/windmill/compare/v1.372.0...v1.373.0) (2024-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Indexing improvements ([#4167](https://github.com/windmill-labs/windmill/issues/4167)) ([edcee6d](https://github.com/windmill-labs/windmill/commit/edcee6d8611ff96bfbca7ba811e5e67beaa80f5a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* app forms default values changes gets propagated ([cd61fc1](https://github.com/windmill-labs/windmill/commit/cd61fc1e8c00cc3a57af0228cdf958b2fb3a2699))
|
||||
|
||||
## [1.372.0](https://github.com/windmill-labs/windmill/compare/v1.371.4...v1.372.0) (2024-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* variables created by password fields expire after 7 days ([b5464e2](https://github.com/windmill-labs/windmill/commit/b5464e2906bdaedb4752e198e40b7e8c4dbb1338))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix raw_deps handling ([efcf0e4](https://github.com/windmill-labs/windmill/commit/efcf0e40f68342f8330cf5d610979eb7fe92ce09))
|
||||
* improve cancel_selection job for running jobs ([42e3ae9](https://github.com/windmill-labs/windmill/commit/42e3ae92e3841579f8ac0821b58ebb72c4204c98))
|
||||
|
||||
## [1.371.4](https://github.com/windmill-labs/windmill/compare/v1.371.3...v1.371.4) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fix recompute all ([#4161](https://github.com/windmill-labs/windmill/issues/4161)) ([10c6997](https://github.com/windmill-labs/windmill/commit/10c699759e1d06d009783b9f5cba2cbf3ba6a7da))
|
||||
* **frontend:** If multiple recompute all present, interval is now in sync ([#4162](https://github.com/windmill-labs/windmill/issues/4162)) ([efeb65b](https://github.com/windmill-labs/windmill/commit/efeb65be4c91491bb4fbedfbc160da7517ebe1d6))
|
||||
* improve index usage and runs page performance ([d69aa8d](https://github.com/windmill-labs/windmill/commit/d69aa8d484de016ae7e7bbd190ce6c254818c861))
|
||||
|
||||
## [1.371.3](https://github.com/windmill-labs/windmill/compare/v1.371.2...v1.371.3) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* for codebase bundle, use cjs exports instead ([be8cedf](https://github.com/windmill-labs/windmill/commit/be8cedfe7fe42105a375c92536ec7f5e5a1343bf))
|
||||
|
||||
## [1.371.2](https://github.com/windmill-labs/windmill/compare/v1.371.1...v1.371.2) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve codebase handling by ignoring creating lock and bundle ([716bb71](https://github.com/windmill-labs/windmill/commit/716bb7118b0d5b8dd86799d5514f37b068613256))
|
||||
|
||||
## [1.371.1](https://github.com/windmill-labs/windmill/compare/v1.371.0...v1.371.1) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* database connections now scale linearly with number of subworkers ([f59046a](https://github.com/windmill-labs/windmill/commit/f59046a9249962ba7e1739de579cd8d885ec63a8))
|
||||
|
||||
## [1.371.0](https://github.com/windmill-labs/windmill/compare/v1.370.0...v1.371.0) (2024-07-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** support array of objects in schema ([#4106](https://github.com/windmill-labs/windmill/issues/4106)) ([5992b82](https://github.com/windmill-labs/windmill/commit/5992b821803d9cdc45fa0dee100f159dd3ed557f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* always consider electron as external for bundles ([06433a6](https://github.com/windmill-labs/windmill/commit/06433a6ec65534e1a11085cefd6a67699e4eb0a0))
|
||||
* fix bunnative lock creation ([6a33624](https://github.com/windmill-labs/windmill/commit/6a33624416cc48e2a1ab21afbd2ac4ab48be2559))
|
||||
* submit and form persistence on app on render change ([28277da](https://github.com/windmill-labs/windmill/commit/28277da35bae1c18f16e85d093989e0d419b3601))
|
||||
* support bunnative in the CLI ([e02e644](https://github.com/windmill-labs/windmill/commit/e02e644d4ca16773bae3cc2a6b52ac08417d3905))
|
||||
|
||||
## [1.370.0](https://github.com/windmill-labs/windmill/compare/v1.369.1...v1.370.0) (2024-07-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* get completed flow node result by api/download + eval list result json path optim ([#4108](https://github.com/windmill-labs/windmill/issues/4108)) ([5031a8c](https://github.com/windmill-labs/windmill/commit/5031a8cb015e3ba8d668f5de769b8d67da82c116))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve native runtime with axios support ([6e91005](https://github.com/windmill-labs/windmill/commit/6e91005daa4bbe2f0bd1f8b1c2cce1f68485f9ef))
|
||||
|
||||
## [1.369.1](https://github.com/windmill-labs/windmill/compare/v1.369.0...v1.369.1) (2024-07-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix lang picker artefact for script editor ([bc94acb](https://github.com/windmill-labs/windmill/commit/bc94acb378b1310271dd3dda80614e0e38b5285f))
|
||||
|
||||
## [1.369.0](https://github.com/windmill-labs/windmill/compare/v1.368.3...v1.369.0) (2024-07-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for text/plain webhook ([#4146](https://github.com/windmill-labs/windmill/issues/4146)) ([eb6557a](https://github.com/windmill-labs/windmill/commit/eb6557a6beb5d3b2324b30f2abc117b1a122c3f7))
|
||||
* **frontend:** App bar as components ([#4103](https://github.com/windmill-labs/windmill/issues/4103)) ([fb89eed](https://github.com/windmill-labs/windmill/commit/fb89eed8fa43568ffe3a22c5a6dd0b9f38890a37))
|
||||
* remove nativets in favor of bun with native pragma ([18f22be](https://github.com/windmill-labs/windmill/commit/18f22be2bae3136f1d9ff06c74f3751504bef60b))
|
||||
* remove nativets in favor of bun with native pragma ([b02baa2](https://github.com/windmill-labs/windmill/commit/b02baa2cfb242f7f6ed64e9861a8562272fb2974))
|
||||
|
||||
## [1.368.3](https://github.com/windmill-labs/windmill/compare/v1.368.2...v1.368.3) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update bun to 1.1.21 ([e45a8a4](https://github.com/windmill-labs/windmill/commit/e45a8a4591649de240a4bff0a25301a2586618bf))
|
||||
|
||||
## [1.368.2](https://github.com/windmill-labs/windmill/compare/v1.368.1...v1.368.2) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add the nobundling option for bun ([c3848e2](https://github.com/windmill-labs/windmill/commit/c3848e2e3011c7e74d5ae6eb85605476ef2998f4))
|
||||
* disable prebundling for nodejs mode scripts ([1e2e907](https://github.com/windmill-labs/windmill/commit/1e2e907982eab2ea7e713d9cfbec046527616fa2))
|
||||
|
||||
## [1.368.1](https://github.com/windmill-labs/windmill/compare/v1.368.0...v1.368.1) (2024-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve runs page performance through pg indices ([99623f3](https://github.com/windmill-labs/windmill/commit/99623f31af109463b065688058d0cab8ea9910de))
|
||||
|
||||
## [1.368.0](https://github.com/windmill-labs/windmill/compare/v1.367.2...v1.368.0) (2024-07-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add FORCE_<env> to allow to override db settings ([f1d5be8](https://github.com/windmill-labs/windmill/commit/f1d5be8a2a76d5c0d37c55ba762f2cd7a6f6982f))
|
||||
* job view audit logging based on env variable ([#4131](https://github.com/windmill-labs/windmill/issues/4131)) ([12f9e56](https://github.com/windmill-labs/windmill/commit/12f9e56154b32d839f49dd967ada4952ca4c395d))
|
||||
* multi sql statement with pg fix ([#4134](https://github.com/windmill-labs/windmill/issues/4134)) ([6df9eca](https://github.com/windmill-labs/windmill/commit/6df9eca2cffef754ee27b3219f18d0ad69615c83))
|
||||
* multi statement sql ([#4104](https://github.com/windmill-labs/windmill/issues/4104)) ([23108b4](https://github.com/windmill-labs/windmill/commit/23108b4688ec9c9683371ee70a50658f6baae42f))
|
||||
* pre-bundle bun scripts ([#4132](https://github.com/windmill-labs/windmill/issues/4132)) ([78fd99c](https://github.com/windmill-labs/windmill/commit/78fd99cb6843c7f8045acd6a6a8df851ec6b35d1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow colors for bun and log error directly ([545a57f](https://github.com/windmill-labs/windmill/commit/545a57fd2c425f02d867f4183aac37131e17210f))
|
||||
* search modal improvements ([#4128](https://github.com/windmill-labs/windmill/issues/4128)) ([c270ab1](https://github.com/windmill-labs/windmill/commit/c270ab11718f8d41fe764d5d2116bb3b9aa20cb2))
|
||||
* support npmjs mode for raw deps of package.json from CLI ([8e615c9](https://github.com/windmill-labs/windmill/commit/8e615c900de46632c52d78a1ec4b6ccc2cec79ff))
|
||||
|
||||
## [1.367.2](https://github.com/windmill-labs/windmill/compare/v1.367.1...v1.367.2) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move bun cache to non mounted volume to benefit from cache optimization ([92dac02](https://github.com/windmill-labs/windmill/commit/92dac027f26dfa880e22b1896b48605ae2ef2794))
|
||||
* use symlink and straight copy as fallback methods for buntar ([80d4fb0](https://github.com/windmill-labs/windmill/commit/80d4fb0352606948b01f4918e11f80a0bef544f4))
|
||||
|
||||
## [1.367.1](https://github.com/windmill-labs/windmill/compare/v1.367.0...v1.367.1) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* delete buntar if any issue while creating it ([204e2fa](https://github.com/windmill-labs/windmill/commit/204e2fafda681347813ab91f96cee4194db2e156))
|
||||
* generate lockfile with npm when npm mode is used ([7fbd002](https://github.com/windmill-labs/windmill/commit/7fbd0028c1f2dfbd6a5cde12e6a0e314ed31552a))
|
||||
|
||||
## [1.367.0](https://github.com/windmill-labs/windmill/compare/v1.366.6...v1.367.0) (2024-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* apply workspace specific tags only to some workspaces ([#4107](https://github.com/windmill-labs/windmill/issues/4107)) ([14a4f12](https://github.com/windmill-labs/windmill/commit/14a4f1282671d1c6a2481123cd5a4297fbd89607))
|
||||
* job search index backed up and loaded from s3 ([#4100](https://github.com/windmill-labs/windmill/issues/4100)) ([cac39a1](https://github.com/windmill-labs/windmill/commit/cac39a105187bd005dee442c4968ebbd9ba163d7))
|
||||
* use hardlinks instead of tar to improve bun cache performances ([48e9f08](https://github.com/windmill-labs/windmill/commit/48e9f089beff10828eef7a35fb341894eb50d25d))
|
||||
* use jwks for external jwt auth ([#4089](https://github.com/windmill-labs/windmill/issues/4089)) ([d096704](https://github.com/windmill-labs/windmill/commit/d096704b27f169672d8949f8570b2b5cba25e78c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cgroupv1 mem limit + granular memory reporting ([#4119](https://github.com/windmill-labs/windmill/issues/4119)) ([13e9e8d](https://github.com/windmill-labs/windmill/commit/13e9e8de9e49f9513ad0cd8ac5a3b8b356de02a9))
|
||||
* filter audit logs end user by username + complete resource filter ([#4105](https://github.com/windmill-labs/windmill/issues/4105)) ([73decb2](https://github.com/windmill-labs/windmill/commit/73decb24b23e1a998957455cf4f4148be5990cce))
|
||||
* fix webhooks urls after BASE_URL change ([d49b2d0](https://github.com/windmill-labs/windmill/commit/d49b2d0f06842e70180f931ac7b4ac683c1c65b6))
|
||||
* make result_json path stable for python executors ([97f6b2e](https://github.com/windmill-labs/windmill/commit/97f6b2e7ad9962c99e566238bde40cdba0ffc3a8))
|
||||
* preserve force json across code preview ([9ab5b2e](https://github.com/windmill-labs/windmill/commit/9ab5b2e32d78056752f654769154b90872a9ab85))
|
||||
* respect sorting when downloading csv f rom auto table ([20390c5](https://github.com/windmill-labs/windmill/commit/20390c53e82686ac6a8b7a9bc1bf9fb8e014e3c5))
|
||||
* sqlx build ([#4120](https://github.com/windmill-labs/windmill/issues/4120)) ([c6dc06b](https://github.com/windmill-labs/windmill/commit/c6dc06bf702b501225a51813a2489ce9ea3c4636))
|
||||
|
||||
## [1.366.6](https://github.com/windmill-labs/windmill/compare/v1.366.5...v1.366.6) (2024-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix copilot completion after base_url change ([ae4cbb0](https://github.com/windmill-labs/windmill/commit/ae4cbb0401b01e1e378b2a982d7a610648c96239))
|
||||
|
||||
## [1.366.5](https://github.com/windmill-labs/windmill/compare/v1.366.4...v1.366.5) (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix BASE_URL build conf ([bb861cf](https://github.com/windmill-labs/windmill/commit/bb861cfedef1e5531f31b62dd73907b89ddd473a))
|
||||
|
||||
## [1.366.4](https://github.com/windmill-labs/windmill/compare/v1.366.3...v1.366.4) (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix BASE_URL build conf ([6e33e4e](https://github.com/windmill-labs/windmill/commit/6e33e4e0b9fce86407cc7cfc9a4a57e03ec5199b))
|
||||
|
||||
## [1.366.3](https://github.com/windmill-labs/windmill/compare/v1.366.2...v1.366.3) (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix BASE_URL build conf ([f6a948b](https://github.com/windmill-labs/windmill/commit/f6a948ba7705b5b59af87bff7d17252ec1c8e739))
|
||||
|
||||
## [1.366.2](https://github.com/windmill-labs/windmill/compare/v1.366.1...v1.366.2) (2024-07-22)
|
||||
|
||||
|
||||
|
||||
10
Caddyfile
10
Caddyfile
@@ -1,3 +1,13 @@
|
||||
{
|
||||
layer4 {
|
||||
:25 {
|
||||
proxy {
|
||||
to windmill_server:2525
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* http://lsp:3001
|
||||
|
||||
36
Dockerfile
36
Dockerfile
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.78-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.79-slim-bookworm
|
||||
ARG PYTHON_IMAGE=python:3.11.8-slim-bookworm
|
||||
|
||||
FROM ${RUST_IMAGE} AS rust_base
|
||||
@@ -41,7 +41,7 @@ COPY /typescript-client/docs/ /frontend/static/tsdocs/
|
||||
|
||||
RUN npm run generate-backend-client
|
||||
ENV NODE_OPTIONS "--max-old-space-size=8192"
|
||||
ARG BASE_URL ""
|
||||
ARG VITE_BASE_URL ""
|
||||
RUN npm run build
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ ARG features=""
|
||||
|
||||
COPY --from=planner /windmill/recipe.json recipe.json
|
||||
|
||||
RUN apt-get update && apt-get install -y libxml2-dev libxmlsec1-dev clang libclang-dev cmake
|
||||
RUN apt-get update && apt-get install -y libxml2-dev=2.9.* libxmlsec1-dev=1.2.* clang=1:14.0-55.* libclang-dev=1:14.0-55.* cmake=3.25.* && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||
@@ -92,14 +94,17 @@ ARG WITH_HELM=true
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y ca-certificates wget curl git jq unzip build-essential unixodbc xmlsec1 software-properties-common \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
RUN if [ "$WITH_POWERSHELL" = "true" ]; then \
|
||||
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then apt-get update -y && apt install libicu-dev -y && wget -O 'pwsh.deb' "https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell_${POWERSHELL_DEB_VERSION}.deb_amd64.deb" && \
|
||||
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then apt-get update -y && apt install libicu-dev -y && wget -O 'pwsh.deb' "https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell_${POWERSHELL_DEB_VERSION}.deb_amd64.deb" && apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* && \
|
||||
dpkg --install 'pwsh.deb' && \
|
||||
rm 'pwsh.deb'; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then apt-get update -y && apt install libicu-dev -y && wget -O powershell.tar.gz "https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-arm64.tar.gz" && \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then apt-get update -y && apt install libicu-dev -y && wget -O powershell.tar.gz "https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-arm64.tar.gz" && apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* && \
|
||||
mkdir -p /opt/microsoft/powershell/7 && \
|
||||
tar zxf powershell.tar.gz -C /opt/microsoft/powershell/7 && \
|
||||
chmod +x /opt/microsoft/powershell/7/pwsh && \
|
||||
@@ -127,13 +132,13 @@ RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
targz='go1.21.6.linux-amd64.tar.gz'; \
|
||||
targz='go1.22.5.linux-amd64.tar.gz'; \
|
||||
;; \
|
||||
'arm64') \
|
||||
targz='go1.21.6.linux-arm64.tar.gz'; \
|
||||
targz='go1.22.5.linux-arm64.tar.gz'; \
|
||||
;; \
|
||||
'armhf') \
|
||||
targz='go1.21.6.linux-armv6l.tar.gz'; \
|
||||
targz='go1.22.5.linux-armv6l.tar.gz'; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \
|
||||
esac; \
|
||||
@@ -143,7 +148,8 @@ ENV PATH="${PATH}:/usr/local/go/bin"
|
||||
ENV GO_PATH=/usr/local/go/bin/go
|
||||
|
||||
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash -
|
||||
RUN apt-get -y update && apt-get install -y curl nodejs awscli
|
||||
RUN apt-get -y update && apt-get install -y curl nodejs awscli && apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# go build is slower the first time it is ran, so we prewarm it in the build
|
||||
RUN mkdir -p /tmp/gobuildwarm && cd /tmp/gobuildwarm && go mod init gobuildwarm && printf "package foo\nimport (\"fmt\")\nfunc main() { fmt.Println(42) }" > warm.go && go mod tidy && go build -x && rm -rf /tmp/gobuildwarm
|
||||
@@ -155,9 +161,9 @@ RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
COPY --from=builder /frontend/build /static_frontend
|
||||
COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
|
||||
|
||||
COPY --from=denoland/deno:1.44.4 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
COPY --from=denoland/deno:1.45.4 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
COPY --from=oven/bun:1.1.18 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.1.21 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
|
||||
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
|
||||
@@ -169,8 +175,12 @@ WORKDIR ${APP}
|
||||
|
||||
RUN ln -s ${APP}/windmill /usr/local/bin/windmill
|
||||
|
||||
RUN windmill cache
|
||||
COPY ./frontend/src/lib/hubPaths.json ${APP}/hubPaths.json
|
||||
|
||||
RUN windmill cache ${APP}/hubPaths.json
|
||||
|
||||
RUN rm ${APP}/hubPaths.json
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["windmill"]
|
||||
CMD ["windmill"]
|
||||
|
||||
45
README.md
45
README.md
@@ -3,7 +3,7 @@
|
||||
</p>
|
||||
|
||||
<p align=center>
|
||||
Open-source developer infrastructure for internal tools (APIs, background jobs, workflows and UIs). Self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs and custom UIs to trigger workflows and scripts as internal apps.
|
||||
Open-source developer infrastructure for internal tools (APIs, background jobs, workflows and UIs). Self-hostable alternative to Retool, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs and custom UIs to trigger workflows and scripts as internal apps.
|
||||
|
||||
<p align=center>
|
||||
Scripts are turned into sharable UIs automatically, and can be composed together into flows or used into richer apps built with low-code. Supported script languages supported are: Python, TypeScript, Go, Bash, SQL, and GraphQL.
|
||||
@@ -110,8 +110,8 @@ You can build your entire infra on top of Windmill!
|
||||
|
||||
```typescript
|
||||
//import any dependency from npm
|
||||
import * as wmill from "https://deno.land/x/windmill@v1.136.0/mod.ts";
|
||||
import cowsay from "npm:cowsay@1.5.0";
|
||||
import * as wmill from "windmill-client"
|
||||
import * as cowsay from 'cowsay@1.5.0';
|
||||
|
||||
// fill the type, or use the +Resource type to get a type-safe reference to a resource
|
||||
type Postgresql = {
|
||||
@@ -131,7 +131,7 @@ export async function main(
|
||||
e = { nested: "object" }
|
||||
//f: wmill.Base64
|
||||
) {
|
||||
const email = Deno.env.get("WM_EMAIL");
|
||||
const email = process.env["WM_EMAIL"];
|
||||
// variables are permissioned and by path
|
||||
let variable = await wmill.getVariable("f/company-folder/my_secret");
|
||||
const lastTimeRun = await wmill.getState();
|
||||
@@ -146,10 +146,8 @@ export async function main(
|
||||
|
||||
## CLI
|
||||
|
||||
We have a powerful CLI to interact with the windmill platform and sync your
|
||||
scripts from local files, github repos and to run scripts and flows on the
|
||||
instance from local commands. See
|
||||
[more details](https://github.com/windmill-labs/windmill/tree/main/cli).
|
||||
We have a powerful CLI to interact with the windmill platform and sync your scripts from local files, GitHub repos and to run scripts and flows on the instance from local commands. See
|
||||
[more details](https://www.windmill.dev/docs/advanced/cli).
|
||||
|
||||

|
||||
|
||||
@@ -165,22 +163,21 @@ Code extension: <https://www.windmill.dev/docs/cli_local_dev/vscode-extension>.
|
||||
|
||||
## Stack
|
||||
|
||||
- Postgres as the database
|
||||
- backend in Rust with the following highly-available and horizontally scalable
|
||||
architecture:
|
||||
- stateless API backend
|
||||
- workers that pull jobs from a queue in Postgres (and later, Kafka or Redis.
|
||||
Upvote [#173](#https://github.com/windmill-labs/windmill/issues/173) if
|
||||
interested )
|
||||
- frontend in Svelte
|
||||
- scripts executions are sandboxed using google's
|
||||
[nsjail](https://github.com/google/nsjail)
|
||||
- javascript runtime is the
|
||||
- Postgres as the database.
|
||||
- Backend in Rust with the following highly-available and horizontally scalable.
|
||||
Architecture:
|
||||
- Stateless API backend.
|
||||
- Workers that pull jobs from a queue in Postgres (and later, Kafka or Redis.
|
||||
Upvote [#173](#https://github.com/windmill-labs/windmill/issues/173) if interested).
|
||||
- Frontend in Svelte.
|
||||
- Scripts executions are sandboxed using Google's
|
||||
[nsjail](https://github.com/google/nsjail).
|
||||
- Javascript runtime is the
|
||||
[deno_core rust library](https://denolib.gitbook.io/guide/) (which itself uses
|
||||
the [rusty_v8](https://github.com/denoland/rusty_v8) and hence V8 underneath)
|
||||
- typescript runtime is deno
|
||||
- python runtime is python3
|
||||
- golang runtime is 1.19.1
|
||||
the [rusty_v8](https://github.com/denoland/rusty_v8) and hence V8 underneath).
|
||||
- TypeScript runtime is Bun and deno.
|
||||
- Python runtime is python3.
|
||||
- Golang runtime is 1.19.1.
|
||||
|
||||
## Fastest Self-Hostable Workflow Engine
|
||||
|
||||
@@ -399,4 +396,4 @@ running options.
|
||||
|
||||
## Copyright
|
||||
|
||||
Windmill Labs, Inc 2023
|
||||
Windmill Labs, Inc 2023
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_status FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow_status",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "061ff848f258dc880bec81d923370c905e689f37c6d931ee4559c3cfd394e168"
|
||||
}
|
||||
@@ -55,7 +55,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
14
backend/.sqlx/query-15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069.json
generated
Normal file
14
backend/.sqlx/query-15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069"
|
||||
}
|
||||
@@ -112,6 +112,11 @@
|
||||
"ordinal": 21,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -141,6 +146,7 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
14
backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json
generated
Normal file
14
backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e"
|
||||
}
|
||||
29
backend/.sqlx/query-28a878c59b6d52f42d315eabb34c96133b69542b01232295c26cc9e093c372f9.json
generated
Normal file
29
backend/.sqlx/query-28a878c59b6d52f42d315eabb34c96133b69542b01232295c26cc9e093c372f9.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now()",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "database_length!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "suspended!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "28a878c59b6d52f42d315eabb34c96133b69542b01232295c26cc9e093c372f9"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,10 +11,11 @@
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Bool"
|
||||
"Bool",
|
||||
"Timestamptz"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2e4115bb2e6c8c85ad1492ad135d6b0454b342126cb5fa17e58caf71b32ee755"
|
||||
"hash": "2be66f23536223549db9b50025932b6b1bad90b8fa47d97acb7d75aa3c37ef86"
|
||||
}
|
||||
20
backend/.sqlx/query-4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77.json
generated
Normal file
20
backend/.sqlx/query-4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')",
|
||||
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -10,11 +10,13 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2ae96e8af2dddd533a0377983ec36e30091089d0a96a37d8c625ddaf85d16a83"
|
||||
"hash": "51a7e36ba1cc1616a19485eb5930cf90df758e85a8cac6037d044d5bd7440681"
|
||||
}
|
||||
@@ -112,6 +112,11 @@
|
||||
"ordinal": 21,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -141,6 +146,7 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "57d830e07e40317150b24cf78248480c3a8b33e3e240385cae5703ccb9b14bec"
|
||||
}
|
||||
@@ -69,7 +69,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND running = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "database_length!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "8481d1bd91b7d23f946b4cf0d312acfc0ed4973309b4860e93882a37a21a0bd0"
|
||||
}
|
||||
@@ -54,7 +54,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
23
backend/.sqlx/query-8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b.json
generated
Normal file
23
backend/.sqlx/query-8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT created_at FROM script WHERE workspace_id = $1 AND hash = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT MAX(created_at) FROM completed_job",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "max",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47"
|
||||
}
|
||||
@@ -54,7 +54,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT worker, worker_instance, vcpus, ping_at, started_at FROM worker_ping WHERE ping_at > now() - interval '30 days'",
|
||||
"query": "SELECT worker, worker_instance, vcpus, memory, ping_at, started_at FROM worker_ping WHERE ping_at > now() - interval '30 days' ORDER BY started_at",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -20,11 +20,16 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "memory",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "ping_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 5,
|
||||
"name": "started_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
@@ -36,9 +41,10 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8480792eefdd0f31a49afec0f91262a6973c068da5413b154eff9b9da90db95f"
|
||||
"hash": "a51007ca7b509b92faa6fe7aa59fa738594a2b7eaab8e38f3ca30e548f9a49a7"
|
||||
}
|
||||
12
backend/.sqlx/query-a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26.json
generated
Normal file
12
backend/.sqlx/query-a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_user_count as (SELECT count(email)::INT as count from active_users),\n active_author_count as (SELECT count(distinct email)::INT as count FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users))\n SELECT active_author_count.count as author_count, active_user_count.count - active_author_count.count as operator_count FROM active_user_count, active_author_count",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "author_count",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "operator_count",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "ad8d9c00bd2d3515ad4afa87aa24b243615484bbad5a793438e23908c3f20c2f"
|
||||
}
|
||||
20
backend/.sqlx/query-b0c2f470f7d2df567eca550db1ae638fcb554622b61a5f4fb6b6696f6283516a.json
generated
Normal file
20
backend/.sqlx/query-b0c2f470f7d2df567eca550db1ae638fcb554622b61a5f4fb6b6696f6283516a.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM variable WHERE expires_at IS NOT NULL AND expires_at < now() RETURNING path",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b0c2f470f7d2df567eca550db1ae638fcb554622b61a5f4fb6b6696f6283516a"
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM completed_job WHERE workspace_id = $1",
|
||||
"query": "SELECT coalesce(COUNT(*), 0) as \"database_length!\", null::bigint as suspended FROM completed_job WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "database_length!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "suspended",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -15,8 +20,9 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f5681df5935ec753f4a6d9ab8b927bbf890f0b4fe2de16dc764b24f24d7bbdca"
|
||||
"hash": "bf04a017bc55f6e1f5f25f697b3c4828611591c76dd32541735095c010d6cdf2"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2, occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, current_job_id = NULL, current_job_workspace_id = NULL WHERE worker = $6",
|
||||
"query": "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2, occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, current_job_id = NULL, current_job_workspace_id = NULL, vcpus = COALESCE($7, vcpus), memory = COALESCE($8, memory) WHERE worker = $6",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -10,10 +10,12 @@
|
||||
"Float4",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
"Text",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4398bc6f701dcba647a259ec2d091a70938a3ecd7ff8a89277cc35d119e2bad4"
|
||||
"hash": "c1de24c68933ab35934395d74c5d2747b3da80c96e255ccf0c4e053206a9ad1b"
|
||||
}
|
||||
15
backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json
generated
Normal file
15
backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5"
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT leaf_jobs->$1::text as leaf_jobs, parent_job FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "leaf_jobs",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "parent_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c8ac658002423906f2a6e43e423a9e24561dfaa6b07ce89063d0910d2342a83b"
|
||||
}
|
||||
38
backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json
generated
Normal file
38
backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "authors",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "operators",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "author_count",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "operator_count",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c"
|
||||
}
|
||||
@@ -75,7 +75,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id FROM queue WHERE created_at <= $1",
|
||||
"query": "SELECT id FROM queue",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -10,13 +10,11 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz"
|
||||
]
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890"
|
||||
"hash": "e207bbcdd478758562e3082330836d6efe1dd6c4f719bc415222eba78d5e63fc"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
"query": "UPDATE script SET lock = $1, created_at = now() WHERE hash = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,5 +12,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8"
|
||||
"hash": "ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT CONCAT(coalesce(completed_job.logs, ''), coalesce(job_logs.logs, '')) as logs FROM completed_job\n LEFT JOIN job_logs ON job_logs.job_id = completed_job.id\n WHERE completed_job.id = $1 AND completed_job.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "logs",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9"
|
||||
}
|
||||
41
backend/.sqlx/query-f04fa0262091c5a4abf7dddafeec1b34ed806dda70592b0030067aed46a104d9.json
generated
Normal file
41
backend/.sqlx/query-f04fa0262091c5a4abf7dddafeec1b34ed806dda70592b0030067aed46a104d9.json
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT created_by, CONCAT(coalesce(completed_job.logs, ''), coalesce(job_logs.logs, '')) as logs, job_logs.log_offset, job_logs.log_file_index\n FROM completed_job\n LEFT JOIN job_logs ON job_logs.job_id = completed_job.id\n WHERE completed_job.id = $1 AND completed_job.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "logs",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "log_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "log_file_index",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f04fa0262091c5a4abf7dddafeec1b34ed806dda70592b0030067aed46a104d9"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false\n GROUP BY script_path), $3) as min_started_at, now() AS now",
|
||||
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND concurrent_limit > 0), $3) as min_started_at, now() AS now",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -26,5 +26,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "3901cce744c9b246b661c817e068bdb3b1ab504ff8070fcccf6c909ad75f1f6d"
|
||||
"hash": "f3d20b0fa17836538ec93b84b08ffc6b555371d6eeeadfc54f5bf0bcbe93d8b4"
|
||||
}
|
||||
14
backend/.sqlx/query-f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225.json
generated
Normal file
14
backend/.sqlx/query-f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE metrics SET value = $1 WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225"
|
||||
}
|
||||
1247
backend/Cargo.lock
generated
1247
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.366.2"
|
||||
version = "1.381.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -25,7 +25,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.366.2"
|
||||
version = "1.381.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -39,7 +39,7 @@ incremental = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-indexer/enterprise"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml"]
|
||||
stripe = ["windmill-api/stripe"]
|
||||
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark"]
|
||||
@@ -47,13 +47,14 @@ flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
|
||||
loki = ["windmill-common/loki"]
|
||||
pg_embed = ["dep:pg-embed"]
|
||||
embedding = ["windmill-api/embedding"]
|
||||
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet"]
|
||||
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "windmill-indexer/parquet"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus"]
|
||||
flow_testing = ["windmill-worker/flow_testing"]
|
||||
openidconnect = ["windmill-api/openidconnect"]
|
||||
cloud = ["windmill-queue/cloud", "windmill-worker/cloud"]
|
||||
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
|
||||
tantivy = ["windmill-indexer/tantivy"]
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -64,7 +65,7 @@ windmill-common = { workspace = true, default-features = false }
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-api = { workspace = true, default-features = false }
|
||||
windmill-worker.workspace = true
|
||||
windmill-indexer.workspace = true
|
||||
windmill-indexer = { workspace = true, optional = true }
|
||||
futures.workspace = true
|
||||
tracing.workspace = true
|
||||
sqlx.workspace = true
|
||||
@@ -155,7 +156,7 @@ mail-send = { version = "0.4.0", features = ["builder"], default-features=false
|
||||
urlencoding = "^2"
|
||||
url = "^2"
|
||||
async-oauth2 = "^0"
|
||||
reqwest = { version = "^0.12", features = ["json", "stream"] }
|
||||
reqwest = { version = "^0.12", features = ["json", "stream", "gzip"] }
|
||||
time = "0.3.16"
|
||||
serde_urlencoded = "^0"
|
||||
tokio-tar = "^0"
|
||||
@@ -164,24 +165,25 @@ tokio-util = { version = "^0", features = ["io"] }
|
||||
json-pointer = "^0"
|
||||
itertools = "^0"
|
||||
regex = "^1"
|
||||
deno_fetch = "0.182.0"
|
||||
deno_tls = "0.145.0"
|
||||
deno_console = "0.158.0"
|
||||
deno_url = "0.158.0"
|
||||
deno_webidl = "0.158.0"
|
||||
deno_web = "0.189.0"
|
||||
deno_net = "0.150.0"
|
||||
deno_core = "0.290.0"
|
||||
deno_ast = { version = "=0.39.2", features = ["transpiling"] }
|
||||
deno_fetch = "0.187.0"
|
||||
deno_tls = "0.150.0"
|
||||
deno_console = "0.163.0"
|
||||
deno_url = "0.163.0"
|
||||
deno_webidl = "0.163.0"
|
||||
deno_web = "0.194.0"
|
||||
deno_net = "0.155.0"
|
||||
deno_core = "0.299.0"
|
||||
deno_ast = { version = "=0.40.0", features = ["transpiling"] }
|
||||
async-recursion = "^1"
|
||||
swc_common = "=0.33.26"
|
||||
swc_ecma_parser = "=0.144.3"
|
||||
swc_ecma_ast = "=0.113.7"
|
||||
swc_ecma_visit = "=0.99.1"
|
||||
base64 = "0.21.0"
|
||||
base32 = "^0"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.6"
|
||||
sqlx = { version = "0.7.3", features = [
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
"macros",
|
||||
"migrate",
|
||||
"uuid",
|
||||
@@ -240,6 +242,9 @@ candle-nn = "0.3.0"
|
||||
tiberius = { git = "https://github.com/prisma/tiberius", rev = "8f66a699dfa041e7b5f736c7e94f92c945453c9e", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]}
|
||||
pin-project = "1"
|
||||
indexmap = { version = "2.2.5", features = ["serde"]}
|
||||
tokio-native-tls = "^0"
|
||||
openssl = "=0.10"
|
||||
mail-parser = "^0"
|
||||
|
||||
datafusion = "39.0.0"
|
||||
object_store = { version = "0.10.0", features = ["aws", "azure"] }
|
||||
|
||||
@@ -1 +1 @@
|
||||
75e2aa1b2a8cff3cdcd64bab33eadab036e0c3fe
|
||||
7cf76a8c6694b59f4615b0c8fa61fb02411326a7
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1 @@
|
||||
CREATE INDEX IF NOT EXISTS queue_concurrency_started_at ON queue (workspace_id, script_path, started_at) WHERE concurrent_limit > 0 AND running = true AND job_kind != 'dependencies' AND canceled = false;-- Add up migration script here
|
||||
1
backend/migrations/20240729064122_addbunnative.down.sql
Normal file
1
backend/migrations/20240729064122_addbunnative.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
2
backend/migrations/20240729064122_addbunnative.up.sql
Normal file
2
backend/migrations/20240729064122_addbunnative.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'bunnative';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
alter table queue drop constraint if exists queue_workspace_id_fkey;
|
||||
alter table completed_job drop constraint if exists completed_job_workspace_id_fkey;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
4
backend/migrations/20240731213058_improve_indices.up.sql
Normal file
4
backend/migrations/20240731213058_improve_indices.up.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
DROP INDEX IF EXISTS queue_sort;
|
||||
CREATE INDEX IF NOT EXISTS queue_sort ON queue (running, tag, priority DESC NULLS LAST, scheduled_for, created_at) WHERE running = false;
|
||||
CREATE INDEX IF NOT EXISTS queue_sort_2 ON queue (running, priority DESC NULLS LAST, scheduled_for, created_at) WHERE running = false;
|
||||
2
backend/migrations/20240801084701_add_expiry.down.sql
Normal file
2
backend/migrations/20240801084701_add_expiry.down.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE variable DROP COLUMN IF EXISTS expires_at;
|
||||
3
backend/migrations/20240801084701_add_expiry.up.sql
Normal file
3
backend/migrations/20240801084701_add_expiry.up.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE variable ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP WITH TIME ZONE;
|
||||
UPDATE variable SET expires_at = (now() + '7 days'::interval) WHERE path LIKE 'u/%/secret_arg/%'
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE workspace_settings DROP COLUMN deploy_ui;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS deploy_ui JSONB;
|
||||
@@ -58,6 +58,7 @@ fn parse_bash_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: default.clone().map(|x| json!(x)),
|
||||
otyp: None,
|
||||
has_default: default.is_some(),
|
||||
oidx: None,
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
@@ -94,6 +95,7 @@ fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: default.clone(),
|
||||
otyp: None,
|
||||
has_default: default.is_some(),
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -130,35 +132,40 @@ non_required="${5:-}"
|
||||
name: "token".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "image".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "digest".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("latest with spaces")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "text".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "non_required".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: None
|
||||
|
||||
@@ -27,7 +27,14 @@ pub fn parse_go_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
.iter()
|
||||
.map(|param| {
|
||||
let (otyp, typ) = parse_go_typ(¶m.typ);
|
||||
Arg { name: get_name(param), otyp, typ, default: None, has_default: false }
|
||||
Arg {
|
||||
name: get_name(param),
|
||||
otyp,
|
||||
typ,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
Ok(MainArgSignature {
|
||||
@@ -179,28 +186,32 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
name: "x".to_string(),
|
||||
typ: Typ::Int,
|
||||
has_default: false,
|
||||
default: None
|
||||
default: None,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("string".to_string()),
|
||||
name: "y".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("bool".to_string()),
|
||||
name: "z".to_string(),
|
||||
typ: Typ::Bool,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("[]string".to_string()),
|
||||
name: "l".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("struct { Name string `json:\"name\"` }".to_string()),
|
||||
@@ -210,21 +221,24 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
|
||||
typ: Box::new(Typ::Str(None))
|
||||
},]),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("interface{}".to_string()),
|
||||
name: "n".to_string(),
|
||||
typ: Typ::Object(vec![]),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("map[string]interface{}".to_string()),
|
||||
name: "m".to_string(),
|
||||
typ: Typ::Object(vec![]),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
],
|
||||
no_main_func: Some(false)
|
||||
|
||||
@@ -51,6 +51,7 @@ fn parse_graphql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: parsed_default,
|
||||
otyp: Some(typ.unwrap()),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,21 +94,24 @@ query($i: Int, $arr: [String]!, $wahoo: String = "wahoo") {
|
||||
name: "i".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("[String]".to_string()),
|
||||
name: "arr".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("String".to_string()),
|
||||
name: "wahoo".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("wahoo")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: None
|
||||
|
||||
@@ -85,6 +85,7 @@ pub fn parse_php_signature(
|
||||
typ,
|
||||
has_default: default.is_some(),
|
||||
default,
|
||||
oidx: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -135,35 +136,40 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
|
||||
name: "input1".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
has_default: true,
|
||||
default: Some(Value::String("hey".to_string()))
|
||||
default: Some(Value::String("hey".to_string())),
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "input2".to_string(),
|
||||
typ: Typ::Bool,
|
||||
has_default: true,
|
||||
default: Some(Value::Bool(false))
|
||||
default: Some(Value::Bool(false)),
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "input3".to_string(),
|
||||
typ: Typ::Int,
|
||||
has_default: true,
|
||||
default: Some(Value::Number(Number::from(3)))
|
||||
default: Some(Value::Number(Number::from(3))),
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "input4".to_string(),
|
||||
typ: Typ::Float,
|
||||
has_default: true,
|
||||
default: Some(Value::Number(Number::from_f64(f64::from(4.5)).unwrap()))
|
||||
default: Some(Value::Number(Number::from_f64(f64::from(4.5)).unwrap())),
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "resource".to_string(),
|
||||
typ: Typ::Resource("stripe".to_string()),
|
||||
has_default: false,
|
||||
default: None
|
||||
default: None,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false)
|
||||
|
||||
@@ -114,6 +114,7 @@ pub fn parse_python_signature(
|
||||
typ,
|
||||
has_default: default.is_some(),
|
||||
default,
|
||||
oidx: None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
@@ -271,49 +272,56 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
name: "test1".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "name".to_string(),
|
||||
typ: Typ::Unknown,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "byte".to_string(),
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "f".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("wewe")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "g".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(21)),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "h".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Int)),
|
||||
default: Some(json!([1, 2])),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "i".to_string(),
|
||||
typ: Typ::Bool,
|
||||
default: Some(json!(true)),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
],
|
||||
no_main_func: Some(false),
|
||||
@@ -352,28 +360,32 @@ def main(test1: str,
|
||||
name: "test1".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "name".to_string(),
|
||||
typ: Typ::Unknown,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "byte".to_string(),
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "resource".to_string(),
|
||||
typ: Typ::Resource("postgresql".to_string()),
|
||||
default: Some(json!("$res:g/all/resource")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false),
|
||||
@@ -407,28 +419,32 @@ def main(test1: str,
|
||||
name: "test1".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "s3o".to_string(),
|
||||
typ: Typ::Resource("S3Object".to_string()),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "name".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("test")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "byte".to_string(),
|
||||
typ: Typ::Bytes,
|
||||
default: Some(json!("<function call>")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false),
|
||||
@@ -459,7 +475,8 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
|
||||
name: "test1".to_string(),
|
||||
typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -469,7 +486,8 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
|
||||
"bar".to_string()
|
||||
])))),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false),
|
||||
@@ -499,7 +517,8 @@ def main(test1: DynSelect_foo): return
|
||||
name: "test1".to_string(),
|
||||
typ: Typ::DynSelect("foo".to_string()),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
}],
|
||||
no_main_func: Some(false),
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ use anyhow::anyhow;
|
||||
use regex::Regex;
|
||||
use serde_json::json;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter::Peekable,
|
||||
str::CharIndices,
|
||||
};
|
||||
pub use windmill_parser::{Arg, MainArgSignature, Typ};
|
||||
|
||||
pub fn parse_mysql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
@@ -62,9 +66,32 @@ pub fn parse_db_resource(code: &str) -> Option<String> {
|
||||
cap.map(|x| x.get(1).map(|x| x.as_str().to_string()).unwrap())
|
||||
}
|
||||
|
||||
pub fn parse_sql_blocks(code: &str) -> Vec<&str> {
|
||||
let mut blocks = vec![];
|
||||
let mut last_idx = 0;
|
||||
|
||||
run_on_sql_statement_matches(
|
||||
code,
|
||||
|char, _| char == ';',
|
||||
|idx, _| {
|
||||
blocks.push(&code[last_idx..=idx]);
|
||||
last_idx = idx + 1;
|
||||
},
|
||||
);
|
||||
if last_idx < code.len() {
|
||||
let last_block = &code[last_idx..];
|
||||
if RE_NONEMPTY_SQL_BLOCK.is_match(last_block) {
|
||||
blocks.push(last_block);
|
||||
}
|
||||
}
|
||||
blocks
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE_CODE_PGSQL: Regex = Regex::new(r#"(?m)\$(\d+)(?:::(\w+(?:\[\])?))?"#).unwrap();
|
||||
|
||||
static ref RE_NONEMPTY_SQL_BLOCK: Regex = Regex::new(r#"(?m)^\s*[^\s](?:[^-]|$)"#).unwrap();
|
||||
|
||||
static ref RE_DB: Regex = Regex::new(r#"(?m)^-- database (\S+) *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
// -- $1 name (type) = default
|
||||
@@ -117,6 +144,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,6 +168,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -147,7 +176,105 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
enum ParserState {
|
||||
Normal,
|
||||
InSingleQuote,
|
||||
InDoubleQuote,
|
||||
InSingleLineComment,
|
||||
InMultiLineComment,
|
||||
}
|
||||
|
||||
fn run_on_sql_statement_matches<
|
||||
F1: FnMut(char, &mut Peekable<CharIndices>) -> bool,
|
||||
F2: FnMut(usize, &mut Peekable<CharIndices>) -> (),
|
||||
>(
|
||||
code: &str,
|
||||
mut cond: F1,
|
||||
mut case: F2,
|
||||
) {
|
||||
let mut chars = code.char_indices().peekable();
|
||||
let mut state = ParserState::Normal;
|
||||
while let Some((idx, char)) = chars.next() {
|
||||
match (&state, char) {
|
||||
(ParserState::Normal, '\'') => {
|
||||
state = ParserState::InSingleQuote;
|
||||
}
|
||||
(ParserState::Normal, '"') => {
|
||||
state = ParserState::InDoubleQuote;
|
||||
}
|
||||
(ParserState::Normal, '-')
|
||||
if chars.peek().is_some_and(|&(_, next_char)| next_char == '-') =>
|
||||
{
|
||||
state = ParserState::InSingleLineComment;
|
||||
}
|
||||
(ParserState::Normal, '/')
|
||||
if chars.peek().is_some_and(|&(_, next_char)| next_char == '*') =>
|
||||
{
|
||||
state = ParserState::InMultiLineComment;
|
||||
}
|
||||
(ParserState::Normal, _) if cond(char, &mut chars) => {
|
||||
case(idx, &mut chars);
|
||||
}
|
||||
(ParserState::InSingleQuote, '\'') => {
|
||||
if chars
|
||||
.peek()
|
||||
.is_some_and(|&(_, next_char)| next_char == '\'')
|
||||
{
|
||||
chars.next(); // skip the escaped single quote
|
||||
} else {
|
||||
state = ParserState::Normal;
|
||||
}
|
||||
}
|
||||
(ParserState::InDoubleQuote, '"') => {
|
||||
if chars.peek().is_some_and(|&(_, next_char)| next_char == '"') {
|
||||
chars.next(); // skip the escaped single quote
|
||||
} else {
|
||||
state = ParserState::Normal;
|
||||
}
|
||||
}
|
||||
(ParserState::InSingleLineComment, '\n') => {
|
||||
state = ParserState::Normal;
|
||||
}
|
||||
(ParserState::InMultiLineComment, '*')
|
||||
if chars.peek().is_some_and(|&(_, next_char)| next_char == '/') =>
|
||||
{
|
||||
state = ParserState::Normal;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_pg_statement_arg_indices(code: &str) -> HashSet<i32> {
|
||||
let mut arg_indices = HashSet::new();
|
||||
run_on_sql_statement_matches(
|
||||
code,
|
||||
|char, chars| {
|
||||
char == '$'
|
||||
&& chars
|
||||
.peek()
|
||||
.is_some_and(|&(_, next_char)| next_char.is_ascii_digit())
|
||||
},
|
||||
|_, chars| {
|
||||
let mut arg_idx = String::new();
|
||||
while let Some(&(_, char)) = chars.peek() {
|
||||
if char.is_ascii_digit() {
|
||||
arg_idx.push(char);
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Ok(arg_idx) = arg_idx.parse::<i32>() {
|
||||
arg_indices.insert(arg_idx);
|
||||
}
|
||||
},
|
||||
);
|
||||
arg_indices
|
||||
}
|
||||
|
||||
fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args = vec![];
|
||||
let mut hm: HashMap<i32, String> = HashMap::new();
|
||||
for cap in RE_CODE_PGSQL.captures_iter(code) {
|
||||
hm.insert(
|
||||
@@ -159,39 +286,74 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
.unwrap_or_else(|| "text".to_string()),
|
||||
);
|
||||
}
|
||||
let mut args = vec![];
|
||||
for i in 1..50 {
|
||||
if hm.contains_key(&i) {
|
||||
let typ = hm.get(&i).unwrap().to_lowercase();
|
||||
args.push(Arg {
|
||||
name: format!("${}", i),
|
||||
typ: parse_pg_typ(typ.as_str()),
|
||||
default: None,
|
||||
otyp: Some(typ),
|
||||
has_default: false,
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
for (i, v) in hm.iter() {
|
||||
let typ = v.to_lowercase();
|
||||
args.push(Arg {
|
||||
name: format!("${}", i),
|
||||
typ: parse_pg_typ(typ.as_str()),
|
||||
default: None,
|
||||
otyp: Some(typ),
|
||||
has_default: false,
|
||||
oidx: Some(*i),
|
||||
});
|
||||
}
|
||||
args.sort_by(|a, b| a.oidx.unwrap().cmp(&b.oidx.unwrap()));
|
||||
for cap in RE_ARG_PGSQL.captures_iter(code) {
|
||||
let i = cap.get(1).and_then(|x| x.as_str().parse::<i32>().ok());
|
||||
if i.is_none() || i.unwrap() as usize > args.len() {
|
||||
continue;
|
||||
}
|
||||
let name = cap.get(2).map(|x| x.as_str().to_string()).unwrap();
|
||||
let default = cap.get(3).map(|x| x.as_str().to_string());
|
||||
let has_default = default.is_some();
|
||||
let oarg = args[(i.unwrap() - 1) as usize].clone();
|
||||
let parsed_default = default.and_then(|x| parsed_default(&oarg.typ, x));
|
||||
let i = cap
|
||||
.get(1)
|
||||
.and_then(|x| x.as_str().parse::<i32>().ok())
|
||||
.map(|x| x);
|
||||
|
||||
args[(i.unwrap() - 1) as usize] =
|
||||
Arg { name, typ: oarg.typ, default: parsed_default, otyp: oarg.otyp, has_default };
|
||||
if let Some(arg_pos) = args
|
||||
.iter()
|
||||
.position(|x| i.is_some_and(|i| x.oidx.unwrap() == i))
|
||||
{
|
||||
let name = cap.get(2).map(|x| x.as_str().to_string()).unwrap();
|
||||
let default = cap.get(3).map(|x| x.as_str().to_string());
|
||||
let has_default = default.is_some();
|
||||
let oarg = args[arg_pos].clone();
|
||||
let parsed_default = default.and_then(|x| parsed_default(&oarg.typ, x));
|
||||
|
||||
args[arg_pos] = Arg {
|
||||
name,
|
||||
typ: oarg.typ,
|
||||
default: parsed_default,
|
||||
otyp: oarg.otyp,
|
||||
has_default,
|
||||
oidx: oarg.oidx,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
pub fn parse_sql_statement_named_params(code: &str, prefix: char) -> HashSet<String> {
|
||||
let mut arg_names = HashSet::new();
|
||||
run_on_sql_statement_matches(
|
||||
code,
|
||||
|char, chars| {
|
||||
char == prefix
|
||||
&& chars
|
||||
.peek()
|
||||
.is_some_and(|&(_, next_char)| next_char.is_alphanumeric() || next_char == '_')
|
||||
},
|
||||
|_, chars| {
|
||||
let mut arg_name = String::new();
|
||||
while let Some(&(_, char)) = chars.peek() {
|
||||
if char.is_alphanumeric() || char == '_' {
|
||||
arg_name.push(char);
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
arg_names.insert(arg_name);
|
||||
},
|
||||
);
|
||||
arg_names
|
||||
}
|
||||
|
||||
fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args: Vec<Arg> = vec![];
|
||||
|
||||
@@ -213,6 +375,7 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -240,6 +403,7 @@ fn parse_snowflake_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -267,6 +431,7 @@ fn parse_mssql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,7 +537,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_sql_sig() -> anyhow::Result<()> {
|
||||
fn test_parse_pgsql_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
|
||||
"#;
|
||||
@@ -388,14 +553,237 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
|
||||
name: "$1".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("bigint".to_string()),
|
||||
name: "$2".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: Some(2),
|
||||
},
|
||||
],
|
||||
no_main_func: None
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pgsql_mutli_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT $3::TEXT, $1::BIGINT;
|
||||
SELECT $2::TEXT;
|
||||
"#;
|
||||
//println!("{}", serde_json::to_string()?);
|
||||
assert_eq!(
|
||||
parse_pgsql_sig(code)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
otyp: Some("bigint".to_string()),
|
||||
name: "param1".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(1),
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
name: "param2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(2),
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
name: "param3".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: Some(3),
|
||||
},
|
||||
],
|
||||
no_main_func: None
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sql_blocks_multi_2semi() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT;
|
||||
-- ;
|
||||
SELECT $2::TEXT;
|
||||
"#;
|
||||
assert_eq!(parse_sql_blocks(code).len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sql_blocks_multi_1semi() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT;
|
||||
-- ;
|
||||
SELECT $2::TEXT
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_sql_blocks(code),
|
||||
vec![
|
||||
r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT;"#,
|
||||
r#"
|
||||
-- ;
|
||||
SELECT $2::TEXT
|
||||
"#
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sql_blocks_single_1semi() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT;
|
||||
-- hey
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_sql_blocks(code),
|
||||
vec![
|
||||
r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT;"#,
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sql_blocks_single_nosemi() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_sql_blocks(code),
|
||||
vec![
|
||||
r#"
|
||||
-- $1 param1
|
||||
-- $2 param2
|
||||
-- $3 param3
|
||||
SELECT '--', ';' $3::TEXT, $1::BIGINT
|
||||
"#
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mysql_positional_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- ? param1 (int) = 3
|
||||
-- ? param2 (text)
|
||||
SELECT ?, ?;
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_mysql_sig(code)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
otyp: Some("int".to_string()),
|
||||
name: "param1".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
name: "param2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
],
|
||||
no_main_func: None
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mysql_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- :param1 (int) = 3
|
||||
-- :param2 (text)
|
||||
-- :param_3 (text)
|
||||
SELECT :param_3, :param1;
|
||||
SELECT :param2;
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_mysql_sig(code)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
otyp: Some("int".to_string()),
|
||||
name: "param1".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
name: "param2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
name: "param_3".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
],
|
||||
no_main_func: None
|
||||
@@ -408,9 +796,10 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
|
||||
#[test]
|
||||
fn test_parse_bigquery_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- @token (string)
|
||||
-- @token (string) = abc
|
||||
-- @image (int64)
|
||||
SELECT * FROM table WHERE token=@token AND image=@image
|
||||
SELECT * FROM table WHERE token=@token AND image=@image;
|
||||
SELECT @token;
|
||||
"#;
|
||||
//println!("{}", serde_json::to_string()?);
|
||||
assert_eq!(
|
||||
@@ -423,15 +812,111 @@ SELECT * FROM table WHERE token=@token AND image=@image
|
||||
otyp: Some("string".to_string()),
|
||||
name: "token".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
default: Some(json!("abc")),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("int64".to_string()),
|
||||
name: "image".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
],
|
||||
no_main_func: None
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_snowflake_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- ? param1 (int) = 3
|
||||
-- ? param2 (varchar)
|
||||
SELECT ?, ?;
|
||||
-- ? param3 (varchar)
|
||||
SELECT ?;
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_snowflake_sig(code)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
otyp: Some("int".to_string()),
|
||||
name: "param1".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
name: "param2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
name: "param3".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
}
|
||||
],
|
||||
no_main_func: None
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mssql_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- @p1 param1 (int) = 3
|
||||
-- @p2 param2 (varchar)
|
||||
-- @p3 param3 (varchar)
|
||||
SELECT @p3, @p1;
|
||||
SELECT @p2;
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_mssql_sig(code)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
otyp: Some("int".to_string()),
|
||||
name: "param1".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(3)),
|
||||
has_default: true,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
name: "param2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("varchar".to_string()),
|
||||
name: "param3".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
],
|
||||
no_main_func: None
|
||||
|
||||
@@ -204,6 +204,7 @@ fn parse_param(
|
||||
typ,
|
||||
default: None,
|
||||
has_default: ident.id.optional || nullable,
|
||||
oidx: None,
|
||||
})
|
||||
}
|
||||
// Pat::Object(ObjectPat { ... }) = todo!()
|
||||
@@ -250,13 +251,13 @@ fn parse_param(
|
||||
if typ == Typ::Unknown && dflt.is_some() {
|
||||
typ = json_to_typ(dflt.as_ref().unwrap());
|
||||
}
|
||||
Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true })
|
||||
Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true, oidx: None })
|
||||
}
|
||||
Pat::Object(ObjectPat { type_ann, .. }) => {
|
||||
let (typ, nullable) = eval_type_ann(&type_ann);
|
||||
*counter += 1;
|
||||
let name = format!("anon{}", counter);
|
||||
Ok(Arg { otyp: None, name, typ, default: None, has_default: nullable })
|
||||
Ok(Arg { otyp: None, name, typ, default: None, has_default: nullable, oidx: None })
|
||||
}
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"parameter syntax unsupported: `{}`: {:#?}",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"collaborators": [
|
||||
"Ruben Fiszel <ruben@windmill.dev>"
|
||||
],
|
||||
"version": "1.364.4",
|
||||
"version": "1.367.2",
|
||||
"files": [
|
||||
"windmill_parser_wasm_bg.wasm",
|
||||
"windmill_parser_wasm.js",
|
||||
@@ -14,4 +14,4 @@
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,20 @@ heap.push(undefined, null, true, false);
|
||||
|
||||
function getObject(idx) { return heap[idx]; }
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let cachedUint8Memory0 = null;
|
||||
@@ -84,20 +98,6 @@ function getInt32Memory0() {
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
let cachedFloat64Memory0 = null;
|
||||
|
||||
function getFloat64Memory0() {
|
||||
@@ -585,6 +585,13 @@ async function __wbg_load(module, imports) {
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg_eval_aa725d466edcea2c = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
@@ -593,13 +600,6 @@ function __wbg_get_imports() {
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_eval_2ea6d5f9a10f336a = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_boolean_get = function(arg0) {
|
||||
const v = getObject(arg0);
|
||||
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
||||
|
||||
Binary file not shown.
@@ -28,77 +28,88 @@ export function main(test1?: string, test2: string = \"burkina\",
|
||||
name: "test1".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("burkina")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test3".to_string(),
|
||||
typ: Typ::Resource("postgres".to_string()),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "b64".to_string(),
|
||||
typ: Typ::Bytes,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "ls".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Bytes)),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "email".to_string(),
|
||||
typ: Typ::Email,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "literal".to_string(),
|
||||
typ: Typ::Str(Some(vec!["test".to_string()])),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "literal_union".to_string(),
|
||||
typ: Typ::Str(Some(vec!["test".to_string(), "test2".to_string()])),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "opt_type".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "opt_type_union".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "opt_type_union_union2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -108,21 +119,24 @@ export function main(test1?: string, test2: string = \"burkina\",
|
||||
ObjectProperty { key: "b".to_string(), typ: Box::new(Typ::Float) }
|
||||
]),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "literals_with_undefined".to_string(),
|
||||
typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])),
|
||||
default: None,
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "dyn_select".to_string(),
|
||||
typ: Typ::DynSelect("foo".to_string()),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false)
|
||||
@@ -154,35 +168,40 @@ export function main(test2 = \"burkina\",
|
||||
name: "test2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("burkina")),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "bool".to_string(),
|
||||
typ: Typ::Bool,
|
||||
default: Some(json!(true)),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "float".to_string(),
|
||||
typ: Typ::Float,
|
||||
default: Some(json!(4.2)),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "int".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(42)),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "ls".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: Some(json!(["test"])),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
@@ -192,7 +211,8 @@ export function main(test2 = \"burkina\",
|
||||
ObjectProperty { key: "b".to_string(), typ: Box::new(Typ::Int) }
|
||||
]),
|
||||
default: Some(json!({"a": "test", "b": 42})),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false)
|
||||
@@ -224,21 +244,24 @@ export function main(foo: FooBar, {a, b}: FooBar, {c, d}: FooBar = {a: \"foo\",
|
||||
otyp: None,
|
||||
typ: Typ::Resource("foo_bar".to_string()),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
name: "anon1".to_string(),
|
||||
otyp: None,
|
||||
typ: Typ::Resource("foo_bar".to_string()),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
name: "anon2".to_string(),
|
||||
otyp: None,
|
||||
typ: Typ::Resource("foo_bar".to_string()),
|
||||
default: Some(json!({"a": "foo", "b": 42})),
|
||||
has_default: true
|
||||
has_default: true,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: Some(false)
|
||||
@@ -268,7 +291,8 @@ export function main(foo: (\"foo\" | \"bar\")[]) {
|
||||
"bar".to_string()
|
||||
])))),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
}],
|
||||
no_main_func: Some(false)
|
||||
}
|
||||
@@ -349,56 +373,64 @@ Write-Output 'Testing...'
|
||||
name: "test_none".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_string".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_int".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_decimal".to_string(),
|
||||
typ: Typ::Float,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_double".to_string(),
|
||||
typ: Typ::Float,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_single".to_string(),
|
||||
typ: Typ::Float,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_datetime_lower".to_string(),
|
||||
typ: Typ::Datetime,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "test_datetime_upper".to_string(),
|
||||
typ: Typ::Datetime,
|
||||
default: None,
|
||||
has_default: false
|
||||
has_default: false,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: None,
|
||||
|
||||
@@ -9,5 +9,5 @@ name = "windmill_parser"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -57,6 +57,7 @@ pub struct Arg {
|
||||
pub typ: Typ,
|
||||
pub default: Option<serde_json::Value>,
|
||||
pub has_default: bool,
|
||||
pub oidx: Option<i32>,
|
||||
}
|
||||
|
||||
pub fn json_to_typ(js: &Value) -> Typ {
|
||||
|
||||
@@ -16,5 +16,4 @@ wasm-bindgen-test.workspace = true
|
||||
[dependencies]
|
||||
windmill-parser.workspace = true
|
||||
windmill-parser-sql.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen.workspace = true
|
||||
@@ -6,15 +6,20 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use anyhow::Context;
|
||||
use gethostname::gethostname;
|
||||
use git_version::git_version;
|
||||
use rand::Rng;
|
||||
use sqlx::{postgres::PgListener, Pool, Postgres};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::fs::DirBuilder;
|
||||
use tokio::{
|
||||
fs::{create_dir_all, DirBuilder, File},
|
||||
io::AsyncReadExt,
|
||||
};
|
||||
use windmill_api::HTTP_CLIENT;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -23,11 +28,11 @@ use windmill_common::ee::schedule_key_renewal;
|
||||
use windmill_common::{
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
|
||||
CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, ENV_SETTINGS,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
|
||||
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
OAUTH_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,
|
||||
},
|
||||
@@ -54,18 +59,19 @@ use windmill_common::METRICS_ADDR;
|
||||
use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
|
||||
|
||||
use windmill_worker::{
|
||||
BUN_CACHE_DIR, BUN_TAR_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
|
||||
GO_BIN_CACHE_DIR, GO_CACHE_DIR, HUB_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
initial_load, load_keep_job_dir, load_metrics_debug_enabled, load_require_preexisting_user,
|
||||
load_tag_per_workspace_enabled, monitor_db, monitor_pool, reload_base_url_setting,
|
||||
reload_bunfig_install_scopes_setting, reload_critical_error_channels_setting,
|
||||
reload_extra_pip_index_url_setting, reload_hub_base_url_setting,
|
||||
reload_job_default_timeout_setting, reload_jwt_secret_setting, reload_license_key,
|
||||
reload_npm_config_registry_setting, reload_pip_index_url_setting,
|
||||
load_tag_per_workspace_enabled, load_tag_per_workspace_workspaces, monitor_db, monitor_pool,
|
||||
reload_base_url_setting, reload_bunfig_install_scopes_setting,
|
||||
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
|
||||
reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting,
|
||||
reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting,
|
||||
reload_retention_period_setting, reload_scim_token_setting, reload_server_config,
|
||||
reload_worker_config,
|
||||
};
|
||||
@@ -111,6 +117,28 @@ pub fn main() -> anyhow::Result<()> {
|
||||
create_and_run_current_thread_inner(windmill_main())
|
||||
}
|
||||
|
||||
async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
let file_path = file_path.unwrap_or("./hubPaths.json".to_string());
|
||||
let mut file = File::open(&file_path)
|
||||
.await
|
||||
.with_context(|| format!("Could not open {}, make sure it exists", &file_path))?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
let paths = serde_json::from_str::<HashMap<String, String>>(&contents).with_context(|| {
|
||||
format!(
|
||||
"Could not parse {}, make sure it is a valid JSON object with string keys and values",
|
||||
&file_path
|
||||
)
|
||||
})?;
|
||||
|
||||
create_dir_all(HUB_CACHE_DIR).await?;
|
||||
|
||||
for path in paths.values() {
|
||||
get_hub_script_content_and_requirements(Some(path.to_string()), None).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn windmill_main() -> anyhow::Result<()> {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
@@ -141,6 +169,9 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
{
|
||||
tracing::warn!("Embeddings are not enabled, ignoring...");
|
||||
}
|
||||
|
||||
cache_hub_scripts(std::env::args().nth(2)).await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
"-v" | "--version" | "version" => {
|
||||
@@ -365,28 +396,35 @@ Windmill Community Edition {GIT_VERSION}
|
||||
|
||||
#[cfg(feature = "tantivy")]
|
||||
let should_index_jobs =
|
||||
mode == Mode::Indexer || (enable_standalone_indexer && mode == Mode::Standalone);
|
||||
|
||||
#[cfg(not(feature = "tantivy"))]
|
||||
let should_index_jobs = false;
|
||||
mode == Mode::Indexer || (enable_standalone_indexer && mode == Mode::Standalone);
|
||||
|
||||
#[cfg(feature = "tantivy")]
|
||||
let (index_reader, index_writer) = if should_index_jobs {
|
||||
let (r, w) = windmill_indexer::indexer_ee::init_index()?;
|
||||
let (r, w) = windmill_indexer::indexer_ee::init_index().await?;
|
||||
(Some(r), Some(w))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let indexer_rx = killpill_rx.resubscribe();
|
||||
let index_writer2 = index_writer.clone();
|
||||
let indexer_f = async {
|
||||
if let Some(index_writer) = index_writer2 {
|
||||
windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx)
|
||||
.await;
|
||||
#[cfg(feature = "tantivy")]
|
||||
let indexer_f = {
|
||||
let indexer_rx = killpill_rx.resubscribe();
|
||||
let index_writer2 = index_writer.clone();
|
||||
async {
|
||||
if let Some(index_writer) = index_writer2 {
|
||||
windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "tantivy"))]
|
||||
let (index_reader, index_writer) = (None, None);
|
||||
|
||||
#[cfg(not(feature = "tantivy"))]
|
||||
let indexer_f = async { Ok(()) as anyhow::Result<()> };
|
||||
|
||||
let server_f = async {
|
||||
if !is_agent {
|
||||
windmill_api::run_server(
|
||||
@@ -398,6 +436,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
server_killpill_rx,
|
||||
base_internal_tx,
|
||||
server_mode,
|
||||
base_internal_url.clone(),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -504,9 +543,14 @@ Windmill Community Edition {GIT_VERSION}
|
||||
},
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING => {
|
||||
if let Err(e) = load_tag_per_workspace_enabled(&db).await {
|
||||
tracing::error!("Error loading default tag per workpsace: {e:#}");
|
||||
tracing::error!("Error loading default tag per workspace: {e:#}");
|
||||
}
|
||||
},
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING => {
|
||||
if let Err(e) = load_tag_per_workspace_workspaces(&db).await {
|
||||
tracing::error!("Error loading default tag per workspace workspaces: {e:#}");
|
||||
}
|
||||
}
|
||||
RETENTION_PERIOD_SECS_SETTING => {
|
||||
reload_retention_period_setting(&db).await
|
||||
},
|
||||
@@ -539,14 +583,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
load_require_preexisting_user(&db).await;
|
||||
},
|
||||
EXPOSE_METRICS_SETTING => {
|
||||
if worker_mode {
|
||||
tracing::info!("Metrics setting changed, restarting");
|
||||
// we wait a bit randomly to avoid having all serverss and workers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..4);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
}
|
||||
tracing::info!("Metrics setting changed, restarting");
|
||||
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..40);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
}
|
||||
},
|
||||
EXPOSE_DEBUG_METRICS_SETTING => {
|
||||
@@ -766,7 +808,8 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
|
||||
DENO_CACHE_DIR_DEPS,
|
||||
DENO_CACHE_DIR_NPM,
|
||||
BUN_CACHE_DIR,
|
||||
BUN_TAR_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR,
|
||||
BUN_BUNDLE_CACHE_DIR,
|
||||
GO_CACHE_DIR,
|
||||
GO_BIN_CACHE_DIR,
|
||||
HUB_CACHE_DIR,
|
||||
@@ -779,6 +822,10 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
|
||||
.expect("could not create initial worker dir");
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
|
||||
*windmill_worker::SLEEP_QUEUE
|
||||
);
|
||||
for i in 1..(num_workers + 1) {
|
||||
let db1 = db.clone();
|
||||
let instance_name = instance_name.clone();
|
||||
@@ -791,7 +838,9 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
|
||||
let hostname = hostname.clone();
|
||||
|
||||
handles.push(tokio::spawn(async move {
|
||||
tracing::info!(worker = %worker_name, "starting worker");
|
||||
if num_workers > 1 {
|
||||
tracing::info!(worker = %worker_name, "starting worker {i}");
|
||||
}
|
||||
|
||||
let f = windmill_worker::run_worker(
|
||||
&db1,
|
||||
|
||||
@@ -28,10 +28,11 @@ use windmill_common::{
|
||||
flow_status::FlowStatusModule,
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_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, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_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,
|
||||
},
|
||||
@@ -41,10 +42,10 @@ use windmill_common::{
|
||||
users::truncate_token,
|
||||
utils::{now_from_db, rd_string},
|
||||
worker::{
|
||||
load_worker_config, reload_custom_tags_setting, DEFAULT_TAGS_PER_WORKSPACE, SERVER_CONFIG,
|
||||
WORKER_CONFIG,
|
||||
load_worker_config, make_pull_query, make_suspended_pull_query, reload_custom_tags_setting,
|
||||
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SERVER_CONFIG, WORKER_CONFIG,
|
||||
},
|
||||
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL,
|
||||
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
|
||||
METRICS_DEBUG_ENABLED, METRICS_ENABLED,
|
||||
};
|
||||
use windmill_queue::cancel_job;
|
||||
@@ -104,8 +105,6 @@ lazy_static::lazy_static! {
|
||||
"Number of jobs in the queue",
|
||||
&["tag"]
|
||||
).unwrap();
|
||||
|
||||
static ref JOB_RETENTION_SECS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));
|
||||
}
|
||||
|
||||
pub async fn initial_load(
|
||||
@@ -127,6 +126,10 @@ pub async fn initial_load(
|
||||
tracing::error!("Error loading default tag per workpsace: {e:#}");
|
||||
}
|
||||
|
||||
if let Err(e) = load_tag_per_workspace_workspaces(db).await {
|
||||
tracing::error!("Error loading default tag per workpsace workspaces: {e:#}");
|
||||
}
|
||||
|
||||
if server_mode {
|
||||
load_require_preexisting_user(db).await;
|
||||
}
|
||||
@@ -204,6 +207,28 @@ pub async fn load_tag_per_workspace_enabled(db: &DB) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_tag_per_workspace_workspaces(db: &DB) -> error::Result<()> {
|
||||
let workspaces = load_value_from_global_settings(db, DEFAULT_TAGS_WORKSPACES_SETTING).await;
|
||||
|
||||
match workspaces {
|
||||
Ok(Some(serde_json::Value::Array(t))) => {
|
||||
let workspaces = t
|
||||
.iter()
|
||||
.filter_map(|x| x.as_str())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
let mut w = DEFAULT_TAGS_WORKSPACES.write().await;
|
||||
*w = Some(workspaces);
|
||||
}
|
||||
Ok(None) => {
|
||||
let mut w = DEFAULT_TAGS_WORKSPACES.write().await;
|
||||
*w = None;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_metrics_debug_enabled(db: &DB) -> error::Result<()> {
|
||||
let metrics_enabled = load_value_from_global_settings(db, EXPOSE_DEBUG_METRICS_SETTING).await;
|
||||
match metrics_enabled {
|
||||
@@ -381,6 +406,21 @@ pub async fn delete_expired_items(db: &DB) -> () {
|
||||
Err(e) => tracing::error!("Error deleting cache resource {}", e.to_string()),
|
||||
}
|
||||
|
||||
let deleted_expired_variables = sqlx::query_scalar!(
|
||||
"DELETE FROM variable WHERE expires_at IS NOT NULL AND expires_at < now() RETURNING path",
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await;
|
||||
|
||||
match deleted_expired_variables {
|
||||
Ok(res) => {
|
||||
if res.len() > 0 {
|
||||
tracing::info!("deleted {} expired variables {:?}", res.len(), res)
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::error!("Error deleting cache resource {}", e.to_string()),
|
||||
}
|
||||
|
||||
let job_retention_secs = *JOB_RETENTION_SECS.read().await;
|
||||
if job_retention_secs > 0 {
|
||||
match db.begin().await {
|
||||
@@ -650,6 +690,16 @@ pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
|
||||
std_env_var: &str,
|
||||
lock: Arc<RwLock<Option<T>>>,
|
||||
) -> error::Result<()> {
|
||||
let force_value = std::env::var(format!("FORCE_{}", std_env_var))
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<T>().ok());
|
||||
|
||||
if let Some(force_value) = force_value {
|
||||
let mut l = lock.write().await;
|
||||
*l = Some(force_value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let q = load_value_from_global_settings(db, setting_name).await?;
|
||||
|
||||
let mut value = std::env::var(std_env_var)
|
||||
@@ -913,6 +963,8 @@ pub async fn reload_worker_config(
|
||||
|
||||
let mut wc = WORKER_CONFIG.write().await;
|
||||
tracing::info!("Reloading worker config...");
|
||||
make_suspended_pull_query(&config).await;
|
||||
make_pull_query(&config).await;
|
||||
*wc = config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ if [ "$REVERT" == "YES" ]; then
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/.}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
if [ "$REVERT_PREVIOUS" == "YES" ]; then
|
||||
git checkout HEAD@{9} ${ce_file} || true
|
||||
git checkout HEAD@{3} ${ce_file} || true
|
||||
else
|
||||
git restore --staged ${ce_file} || true
|
||||
git restore ${ce_file} || true
|
||||
|
||||
@@ -22,7 +22,7 @@ use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs};
|
||||
|
||||
use serde::Serialize;
|
||||
use windmill_common::auth::JWT_SECRET;
|
||||
use windmill_common::worker::{PriorityTags, WORKER_CONFIG};
|
||||
use windmill_common::worker::WORKER_CONFIG;
|
||||
use windmill_common::{
|
||||
flow_status::{FlowStatus, FlowStatusModule, RestartedFrom},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
|
||||
@@ -128,6 +128,7 @@ impl ApiServer {
|
||||
rx,
|
||||
port_tx,
|
||||
false,
|
||||
format!("http://localhost:{}", addr.port()),
|
||||
));
|
||||
|
||||
_port_rx.await.unwrap();
|
||||
@@ -371,7 +372,7 @@ mod suspend_resume {
|
||||
server.close().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
json!( {"error": {"name": "SuspendedDisapproved", "message": "Disapproved by Some(\"ruben\")"}}),
|
||||
json!( {"error": {"name": "SuspendedDisapproved", "message": "Disapproved by ruben"}}),
|
||||
result
|
||||
);
|
||||
}
|
||||
@@ -432,7 +433,7 @@ mod suspend_resume {
|
||||
let result = completed_job(flow, &db).await.json_result().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
json!( {"error": {"name": "SuspendedDisapproved", "message": "Disapproved by None"}}),
|
||||
json!( {"error": {"name": "SuspendedDisapproved", "message": "Disapproved by unknown"}}),
|
||||
result
|
||||
);
|
||||
}
|
||||
@@ -1007,8 +1008,12 @@ fn spawn_test_worker(
|
||||
{
|
||||
let mut wc = WORKER_CONFIG.write().await;
|
||||
(*wc).worker_tags = windmill_common::worker::DEFAULT_TAGS.clone();
|
||||
(*wc).priority_tags_sorted =
|
||||
vec![PriorityTags { priority: 0, tags: (*wc).worker_tags.clone() }]
|
||||
(*wc).priority_tags_sorted = vec![windmill_common::worker::PriorityTags {
|
||||
priority: 0,
|
||||
tags: (*wc).worker_tags.clone(),
|
||||
}];
|
||||
windmill_common::worker::make_suspended_pull_query(&wc).await;
|
||||
windmill_common::worker::make_pull_query(&wc).await;
|
||||
}
|
||||
windmill_worker::run_worker::<rsmq_async::MultiplexedRsmq>(
|
||||
&db,
|
||||
|
||||
@@ -18,6 +18,7 @@ embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core"
|
||||
parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus"]
|
||||
openidconnect = ["dep:openidconnect"]
|
||||
tantivy = ["dep:windmill-indexer"]
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
@@ -27,7 +28,7 @@ windmill-parser.workspace = true
|
||||
windmill-parser-py-imports.workspace = true
|
||||
windmill-parser-ts.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-indexer.workspace = true
|
||||
windmill-indexer = { workspace = true, optional = true }
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
argon2.workspace = true
|
||||
@@ -50,6 +51,7 @@ chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
hex.workspace = true
|
||||
base64.workspace = true
|
||||
base32.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
cron.workspace = true
|
||||
mime_guess.workspace = true
|
||||
@@ -58,6 +60,10 @@ tracing-subscriber.workspace = true
|
||||
quick_cache.workspace = true
|
||||
rand.workspace = true
|
||||
time.workspace = true
|
||||
native-tls.workspace = true
|
||||
tokio-native-tls.workspace = true
|
||||
openssl.workspace = true
|
||||
mail-parser = { workspace = true, features = ["serde_support"] }
|
||||
magic-crypt.workspace = true
|
||||
tempfile.workspace = true
|
||||
tokio-util.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.366.2
|
||||
version: 1.381.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -845,6 +845,12 @@ paths:
|
||||
operationId: renewLicenseKey
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- name: license_key
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
@@ -859,6 +865,12 @@ paths:
|
||||
operationId: createCustomerPortalSession
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- name: license_key
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: url to portal
|
||||
@@ -1507,6 +1519,8 @@ paths:
|
||||
$ref: "#/components/schemas/LargeFileStorage"
|
||||
git_sync:
|
||||
$ref: "#/components/schemas/WorkspaceGitSyncSettings"
|
||||
deploy_ui:
|
||||
$ref: "#/components/schemas/WorkspaceDeployUISettings"
|
||||
default_app:
|
||||
type: string
|
||||
default_scripts:
|
||||
@@ -1896,6 +1910,32 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/workspaces/edit_deploy_ui_config:
|
||||
post:
|
||||
summary: edit workspace deploy ui settings
|
||||
operationId: editWorkspaceDeployUISettings
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: Workspace deploy UI settings
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deploy_ui_settings:
|
||||
$ref: "#/components/schemas/WorkspaceDeployUISettings"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/workspaces/edit_default_app:
|
||||
post:
|
||||
summary: edit default app for workspace
|
||||
@@ -2444,6 +2484,8 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
"200":
|
||||
description: variable list
|
||||
@@ -5589,6 +5631,8 @@ paths:
|
||||
properties:
|
||||
database_length:
|
||||
type: integer
|
||||
suspended:
|
||||
type: integer
|
||||
required:
|
||||
- database_length
|
||||
|
||||
@@ -5770,6 +5814,7 @@ paths:
|
||||
- $ref: "#/components/parameters/CreatedOrStartedAfter"
|
||||
- $ref: "#/components/parameters/CreatedOrStartedAfterCompletedJob"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/Suspended"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/Tag"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
@@ -9770,6 +9815,9 @@ components:
|
||||
type: boolean
|
||||
is_refreshed:
|
||||
type: boolean
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- workspace_id
|
||||
- path
|
||||
@@ -9808,6 +9856,9 @@ components:
|
||||
type: integer
|
||||
is_oauth:
|
||||
type: boolean
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -11070,6 +11121,25 @@ components:
|
||||
items:
|
||||
$ref: "#/components/schemas/GitRepositorySettings"
|
||||
|
||||
WorkspaceDeployUISettings:
|
||||
type: object
|
||||
properties:
|
||||
include_path:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
include_type:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- script
|
||||
- flow
|
||||
- app
|
||||
- resource
|
||||
- variable
|
||||
- secret
|
||||
|
||||
WorkspaceDefaultScripts:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -705,7 +705,7 @@ pub async fn get_hub_app_by_id(
|
||||
&format!("{}/apps/{}/json", *HUB_BASE_URL.read().await, id),
|
||||
false,
|
||||
None,
|
||||
&db,
|
||||
Some(&db),
|
||||
)
|
||||
.await?
|
||||
.json()
|
||||
@@ -1383,7 +1383,30 @@ async fn build_args(
|
||||
|
||||
let arg_str = v.get();
|
||||
|
||||
if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
if arg_str.starts_with("\"$ctx:") {
|
||||
let prop = arg_str.trim_start_matches("\"$ctx:").trim_end_matches("\"");
|
||||
let value = match prop {
|
||||
"username" => authed.as_ref().map(|a| {
|
||||
serde_json::to_value(a.username_override.as_ref().unwrap_or(&a.username))
|
||||
}),
|
||||
"email" => authed.as_ref().map(|a| serde_json::to_value(&a.email)),
|
||||
"workspace" => Some(serde_json::to_value(&w_id)),
|
||||
"groups" => authed.as_ref().map(|a| serde_json::to_value(&a.groups)),
|
||||
"author" => Some(serde_json::to_value(&policy.on_behalf_of_email)),
|
||||
_ => {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"context variable {} not allowed",
|
||||
prop
|
||||
)))
|
||||
}
|
||||
};
|
||||
safe_args.insert(
|
||||
k.to_string(),
|
||||
to_raw_value(&value.unwrap_or(Ok(serde_json::Value::Null)).map_err(|e| {
|
||||
Error::InternalErr(format!("failed to serialize ctx variable for {}: {}", k, e))
|
||||
})?),
|
||||
);
|
||||
} else if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
safe_args.insert(k.to_string(), v);
|
||||
} else {
|
||||
safe_args.insert(
|
||||
|
||||
@@ -249,14 +249,57 @@ async fn fix_flow_versioning_migration(
|
||||
}
|
||||
|
||||
async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
|
||||
// let has_done_migration = sqlx::query_scalar!(
|
||||
// "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
|
||||
// )
|
||||
// .fetch_one(db)
|
||||
// .await?
|
||||
// .unwrap_or(false);
|
||||
// if !has_done_migration {
|
||||
// tracing::info!("Applying fix_job_completed_index migration");
|
||||
// let mut tx = db.begin().await?;
|
||||
// let mut r = false;
|
||||
// while !r {
|
||||
// r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
|
||||
// .fetch_one(&mut *tx)
|
||||
// .await
|
||||
// .map_err(|e| {
|
||||
// tracing::error!("Error acquiring fix_job_completed_index lock: {e:#}");
|
||||
// sqlx::migrate::MigrateError::Execute(e)
|
||||
// })?
|
||||
// .unwrap_or(false);
|
||||
// if !r {
|
||||
// tracing::info!("PG fix_job_completed_index_migration lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
// tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
// }
|
||||
// }
|
||||
// // sqlx::query(
|
||||
// // "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new ON completed_job (workspace_id, job_kind, is_skipped, is_flow_step, created_at DESC, started_at DESC)"
|
||||
// // ).execute(db).await?;
|
||||
|
||||
// sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
|
||||
// .execute(db)
|
||||
// .await?;
|
||||
|
||||
// sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING")
|
||||
// .execute(&mut *tx)
|
||||
// .await?;
|
||||
// let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
// .execute(&mut *tx)
|
||||
// .await?;
|
||||
// tx.commit().await?;
|
||||
// }
|
||||
|
||||
let migration_job_name = "fix_job_completed_index_2";
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !has_done_migration {
|
||||
tracing::info!("Applying fix_job_completed_index migration");
|
||||
tracing::info!("Applying {migration_job_name} migration");
|
||||
let mut tx = db.begin().await?;
|
||||
let mut r = false;
|
||||
while !r {
|
||||
@@ -264,30 +307,266 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error acquiring fix_job_completed_index lock: {e:#}");
|
||||
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
|
||||
sqlx::migrate::MigrateError::Execute(e)
|
||||
})?
|
||||
.unwrap_or(false);
|
||||
if !r {
|
||||
tracing::info!("PG fix_job_completed_index_migration lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
sqlx::query(
|
||||
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new ON completed_job (workspace_id, job_kind, is_skipped, is_flow_step, created_at DESC, started_at DESC)"
|
||||
).execute(db).await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
|
||||
tracing::info!("acquired lock for {migration_job_name}");
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_done_migration {
|
||||
// sqlx::query(
|
||||
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
|
||||
// ).execute(db).await?;
|
||||
|
||||
// sqlx::query(
|
||||
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
|
||||
// ).execute(db).await?;
|
||||
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING")
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
migration_job_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tracing::info!("Finished applying {migration_job_name} migration");
|
||||
} else {
|
||||
tracing::info!("migration {migration_job_name} already done");
|
||||
}
|
||||
|
||||
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
tracing::info!("released lock for {migration_job_name}");
|
||||
}
|
||||
|
||||
let migration_job_name = "fix_job_completed_index_3";
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !has_done_migration {
|
||||
tracing::info!("Applying {migration_job_name} migration");
|
||||
let mut tx = db.begin().await?;
|
||||
let mut r = false;
|
||||
while !r {
|
||||
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
|
||||
sqlx::migrate::MigrateError::Execute(e)
|
||||
})?
|
||||
.unwrap_or(false);
|
||||
if !r {
|
||||
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
tracing::info!("acquired lock for {migration_job_name}");
|
||||
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_done_migration {
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_job_on_schedule_path")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_queue")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_on_created")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
migration_job_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tracing::info!("Finished applying {migration_job_name} migration");
|
||||
} else {
|
||||
tracing::info!("migration {migration_job_name} already done");
|
||||
}
|
||||
|
||||
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
tracing::info!("released lock for {migration_job_name}");
|
||||
}
|
||||
|
||||
let migration_job_name = "fix_job_completed_index_4";
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !has_done_migration {
|
||||
tracing::info!("Applying {migration_job_name} migration");
|
||||
let mut tx = db.begin().await?;
|
||||
let mut r = false;
|
||||
while !r {
|
||||
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
|
||||
sqlx::migrate::MigrateError::Execute(e)
|
||||
})?
|
||||
.unwrap_or(false);
|
||||
if !r {
|
||||
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
tracing::info!("acquired lock for {migration_job_name}");
|
||||
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_done_migration {
|
||||
let mut i = 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_3 ON completed_job (workspace_id, created_at DESC)")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_8 ON completed_job (workspace_id, created_at DESC) where job_kind in ('deploymentcallback') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_9 ON completed_job (workspace_id, created_at DESC) where job_kind in ('dependencies', 'flowdependencies', 'appdependencies') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_5 ON completed_job (workspace_id, created_at DESC) where job_kind in ('preview', 'flowpreview') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_6 ON completed_job (workspace_id, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_7 ON completed_job (workspace_id, success, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON completed_job (workspace_id, started_at DESC)")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists root_job_index_by_path_2 ON completed_job (workspace_id, script_path, created_at desc) WHERE parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_created_at ON completed_job (created_at DESC)")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
migration_job_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tracing::info!("Finished applying {migration_job_name} migration");
|
||||
} else {
|
||||
tracing::info!("migration {migration_job_name} already done");
|
||||
}
|
||||
|
||||
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
tracing::info!("released lock for {migration_job_name}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use anyhow::anyhow;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<String> {
|
||||
// Implementation is not open source
|
||||
@@ -9,8 +13,20 @@ pub async fn validate_license_key(_license_key: String) -> anyhow::Result<String
|
||||
pub async fn jwt_ext_auth(
|
||||
_w_id: Option<&String>,
|
||||
_token: &str,
|
||||
) -> Option<(crate::db::ApiAuthed, usize)> {
|
||||
_external_jwks: Option<Arc<RwLock<ExternalJwks>>>,
|
||||
) -> anyhow::Result<(crate::db::ApiAuthed, usize)> {
|
||||
// Implementation is not open source
|
||||
|
||||
None
|
||||
Err(anyhow!("External JWT auth is not open source"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub struct ExternalJwks;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
impl ExternalJwks {
|
||||
pub async fn load() -> Option<Arc<RwLock<Self>>> {
|
||||
// Implementation is not open source
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ impl EmbeddingsDb {
|
||||
&format!("{}/scripts/embeddings", hub_base_url),
|
||||
false,
|
||||
None,
|
||||
pg_db,
|
||||
Some(pg_db),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
@@ -326,7 +326,7 @@ impl EmbeddingsDb {
|
||||
&format!("{}/scripts/embeddings", hub_base_url),
|
||||
false,
|
||||
None,
|
||||
pg_db,
|
||||
Some(pg_db),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
@@ -375,7 +375,7 @@ impl EmbeddingsDb {
|
||||
&format!("{}/resource_types/embeddings", hub_base_url),
|
||||
false,
|
||||
None,
|
||||
pg_db,
|
||||
Some(pg_db),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
@@ -388,7 +388,7 @@ impl EmbeddingsDb {
|
||||
&format!("{}/resource_types/embeddings", hub_base_url),
|
||||
false,
|
||||
None,
|
||||
pg_db,
|
||||
Some(pg_db),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ pub async fn get_hub_flow_by_id(
|
||||
&format!("{}/flows/{}/json", *HUB_BASE_URL.read().await, id),
|
||||
false,
|
||||
None,
|
||||
&db,
|
||||
Some(&db),
|
||||
)
|
||||
.await?
|
||||
.json()
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::HeaderValue;
|
||||
use quick_cache::sync::Cache;
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::Pool;
|
||||
use std::collections::HashMap;
|
||||
@@ -183,7 +184,10 @@ pub fn workspaced_service() -> Router {
|
||||
.layer(ce_headers.clone()),
|
||||
)
|
||||
.route("/run/preview", post(run_preview_script))
|
||||
.route("/run/preview_bundle", post(run_bundle_preview_script))
|
||||
.route(
|
||||
"/run/preview_bundle",
|
||||
post(run_bundle_preview_script).layer(axum::extract::DefaultBodyLimit::disable()),
|
||||
)
|
||||
.route("/add_batch_jobs/:n", post(add_batch_jobs))
|
||||
.route("/run/preview_flow", post(run_preview_flow_job))
|
||||
.route(
|
||||
@@ -297,11 +301,17 @@ struct JsonPath {
|
||||
pub approver: Option<String>,
|
||||
}
|
||||
async fn get_result_by_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, flow_id, node_id)): Path<(String, Uuid, String)>,
|
||||
Query(JsonPath { json_path, .. }): Query<JsonPath>,
|
||||
) -> windmill_common::error::JsonResult<Box<JsonRawValue>> {
|
||||
let res = windmill_queue::get_result_by_id(db, w_id, flow_id, node_id, json_path).await?;
|
||||
let res =
|
||||
windmill_queue::get_result_by_id(db.clone(), w_id.clone(), flow_id, node_id, json_path)
|
||||
.await?;
|
||||
|
||||
log_job_view(&db, Some(&authed), &w_id, &flow_id).await?;
|
||||
|
||||
Ok(Json(res))
|
||||
}
|
||||
|
||||
@@ -622,6 +632,9 @@ async fn get_flow_job_debug_info(
|
||||
jobs.insert(job.id().to_string(), job);
|
||||
}
|
||||
}
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(Json(jobs).into_response())
|
||||
} else {
|
||||
Err(error::Error::NotFound(format!(
|
||||
@@ -651,6 +664,9 @@ async fn get_job(
|
||||
)
|
||||
.await?;
|
||||
job.fetch_outstanding_wait_time(&db).await?;
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(Json(job).into_response())
|
||||
}
|
||||
|
||||
@@ -871,6 +887,9 @@ async fn get_job_logs(
|
||||
));
|
||||
}
|
||||
let logs = record.logs.unwrap_or_default();
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(r) = get_logs_from_store(record.log_offset, &logs, &record.log_file_index).await
|
||||
{
|
||||
@@ -900,6 +919,9 @@ async fn get_job_logs(
|
||||
));
|
||||
}
|
||||
let logs = text.logs.unwrap_or_default();
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(r) = get_logs_from_store(text.log_offset, &logs, &text.log_file_index).await {
|
||||
return r.map(content_plain);
|
||||
@@ -938,6 +960,9 @@ async fn get_args(
|
||||
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(Json(record.args.map(|x| x.0).unwrap_or_default()))
|
||||
} else {
|
||||
let record = sqlx::query_as::<_, RawArgs>(
|
||||
@@ -955,6 +980,9 @@ async fn get_args(
|
||||
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(Json(record.args.map(|x| x.0).unwrap_or_default()))
|
||||
}
|
||||
}
|
||||
@@ -1006,7 +1034,7 @@ pub struct ListableCompletedJob {
|
||||
pub labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[derive(Deserialize, Clone, Default)]
|
||||
pub struct RunJobQuery {
|
||||
scheduled_for: Option<chrono::DateTime<chrono::Utc>>,
|
||||
scheduled_in_secs: Option<i64>,
|
||||
@@ -1368,7 +1396,7 @@ async fn cancel_jobs(
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM queue WHERE id = any($1) AND workspace_id = $2",
|
||||
&jobs,
|
||||
&trivial_jobs,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -1434,8 +1462,6 @@ async fn cancel_selection(
|
||||
Path(w_id): Path<String>,
|
||||
Json(jobs): Json<Vec<Uuid>>,
|
||||
) -> error::JsonResult<Vec<Uuid>> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let jobs_to_cancel = sqlx::query_scalar!(
|
||||
"SELECT id FROM queue WHERE id = ANY($1) AND schedule_path IS NULL",
|
||||
@@ -1481,6 +1507,7 @@ async fn list_filtered_uuids(
|
||||
#[derive(Serialize, Debug, FromRow)]
|
||||
struct QueueStats {
|
||||
database_length: i64,
|
||||
suspended: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -1496,7 +1523,7 @@ async fn count_queue_jobs(
|
||||
Ok(Json(
|
||||
sqlx::query_as!(
|
||||
QueueStats,
|
||||
"SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND running = false",
|
||||
"SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now()",
|
||||
w_id,
|
||||
w_id == "admins" && cq.all_workspaces.unwrap_or(false),
|
||||
)
|
||||
@@ -1512,7 +1539,7 @@ async fn count_completed_jobs(
|
||||
Ok(Json(
|
||||
sqlx::query_as!(
|
||||
QueueStats,
|
||||
"SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM completed_job WHERE workspace_id = $1",
|
||||
"SELECT coalesce(COUNT(*), 0) as \"database_length!\", null::bigint as suspended FROM completed_job WHERE workspace_id = $1",
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
@@ -1944,7 +1971,7 @@ pub async fn get_suspended_job_flow(
|
||||
Job::QueuedJob(job) => &job.email,
|
||||
Job::CompletedJobWithFormattedResult(job) => &job.cj.email,
|
||||
};
|
||||
conditionally_require_authed_user(authed, flow_status.clone(), trigger_email)?;
|
||||
conditionally_require_authed_user(authed.clone(), flow_status.clone(), trigger_email)?;
|
||||
|
||||
let approvers_from_status = match flow_module_status {
|
||||
FlowStatusModule::Success { approvers, .. } => approvers.to_owned(),
|
||||
@@ -1971,6 +1998,8 @@ pub async fn get_suspended_job_flow(
|
||||
approvers_from_status
|
||||
};
|
||||
|
||||
log_job_view(&db, authed.as_ref(), &w_id, &job).await?;
|
||||
|
||||
Ok(Json(SuspendedJobFlow { job: flow, approvers }).into_response())
|
||||
}
|
||||
|
||||
@@ -2584,6 +2613,7 @@ enum PreviewKind {
|
||||
Http,
|
||||
Noop,
|
||||
Bundle,
|
||||
Tarbundle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -2721,6 +2751,23 @@ pub async fn run_flow_by_path(
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
args: PushArgsOwned,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
run_flow_by_path_inner(
|
||||
authed, db, user_db, rsmq, w_id, flow_path, run_query, args, None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_flow_by_path_inner(
|
||||
authed: ApiAuthed,
|
||||
db: DB,
|
||||
user_db: UserDB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
w_id: String,
|
||||
flow_path: StripPath,
|
||||
run_query: RunJobQuery,
|
||||
args: PushArgsOwned,
|
||||
label_prefix: Option<String>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
check_license_key_valid().await?;
|
||||
@@ -2754,7 +2801,9 @@ pub async fn run_flow_by_path(
|
||||
&w_id,
|
||||
JobPayload::Flow { path: flow_path.to_string(), dedicated_worker },
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
&label_prefix
|
||||
.map(|x| x + authed.display_username())
|
||||
.unwrap_or_else(|| authed.display_username().to_string()),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
@@ -2880,6 +2929,31 @@ pub async fn run_script_by_path(
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
args: PushArgsOwned,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
run_script_by_path_inner(
|
||||
authed,
|
||||
db,
|
||||
user_db,
|
||||
rsmq,
|
||||
w_id,
|
||||
script_path,
|
||||
run_query,
|
||||
args,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_script_by_path_inner(
|
||||
authed: ApiAuthed,
|
||||
db: DB,
|
||||
user_db: UserDB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
w_id: String,
|
||||
script_path: StripPath,
|
||||
run_query: RunJobQuery,
|
||||
args: PushArgsOwned,
|
||||
label_prefix: Option<String>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
check_license_key_valid().await?;
|
||||
@@ -2905,7 +2979,9 @@ pub async fn run_script_by_path(
|
||||
&w_id,
|
||||
job_payload,
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
&label_prefix
|
||||
.map(|x| x + authed.display_username())
|
||||
.unwrap_or_else(|| authed.display_username().to_string()),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
@@ -3256,6 +3332,76 @@ lazy_static::lazy_static! {
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(200);
|
||||
|
||||
static ref JOB_VIEW_AUDIT_LOGS: bool = std::env::var("JOB_VIEW_AUDIT_LOGS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
static ref JOB_VIEW_CACHE: JobViewCache = JobViewCache::new(50000);
|
||||
}
|
||||
|
||||
struct JobViewCache {
|
||||
cache: Cache<String, std::time::Instant>,
|
||||
}
|
||||
|
||||
impl JobViewCache {
|
||||
fn new(items_capacity: usize) -> Self {
|
||||
Self { cache: Cache::new(items_capacity) }
|
||||
}
|
||||
fn get_or_insert(&self, key: &str) -> Option<std::time::Instant> {
|
||||
match self.cache.get(key) {
|
||||
Some(t) if t < std::time::Instant::now() => {
|
||||
self.cache.insert(
|
||||
key.to_string(),
|
||||
std::time::Instant::now() + std::time::Duration::from_secs(60),
|
||||
);
|
||||
None
|
||||
}
|
||||
v => {
|
||||
self.cache.insert(
|
||||
key.to_string(),
|
||||
std::time::Instant::now() + std::time::Duration::from_secs(60),
|
||||
);
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn log_job_view(
|
||||
db: &DB,
|
||||
opt_authed: Option<&ApiAuthed>,
|
||||
w_id: &str,
|
||||
job_id: &Uuid,
|
||||
) -> error::Result<()> {
|
||||
if *JOB_VIEW_AUDIT_LOGS {
|
||||
let audit_author = match opt_authed {
|
||||
Some(authed) => AuditAuthor::from(authed),
|
||||
None => AuditAuthor {
|
||||
username: "anonymous".to_string(),
|
||||
username_override: None,
|
||||
email: "anonymous".to_string(),
|
||||
},
|
||||
};
|
||||
if JOB_VIEW_CACHE
|
||||
.get_or_insert(&format!("{}_{}", job_id, audit_author.email))
|
||||
.is_none()
|
||||
{
|
||||
audit_log(
|
||||
db,
|
||||
&audit_author,
|
||||
"jobs.view",
|
||||
ActionKind::Execute,
|
||||
w_id,
|
||||
Some(&job_id.to_string()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_job_by_path_get(
|
||||
@@ -3713,6 +3859,8 @@ async fn run_bundle_preview_script(
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
mut multipart: axum::extract::Multipart,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
use windmill_common::scripts::PREVIEW_IS_TAR_CODEBASE_HASH;
|
||||
|
||||
check_license_key_valid().await?;
|
||||
|
||||
check_scopes(&authed, || format!("runscript"))?;
|
||||
@@ -3725,9 +3873,12 @@ async fn run_bundle_preview_script(
|
||||
let mut job_id = None;
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
let mut is_tar = false;
|
||||
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
let data = field.bytes().await;
|
||||
let data = data.map_err(to_anyhow)?;
|
||||
if name == "preview" {
|
||||
let preview: Preview = serde_json::from_slice(&data).map_err(to_anyhow)?;
|
||||
|
||||
@@ -3739,27 +3890,33 @@ async fn run_bundle_preview_script(
|
||||
|
||||
let args = preview.args.unwrap_or_default();
|
||||
|
||||
is_tar = match preview.kind {
|
||||
Some(PreviewKind::Tarbundle) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// tracing::info!("is_tar 1: {is_tar}");
|
||||
// hmap.insert("")
|
||||
let (uuid, ntx) = push(
|
||||
&db,
|
||||
ltx,
|
||||
&w_id,
|
||||
match preview.kind {
|
||||
Some(PreviewKind::Identity) => JobPayload::Identity,
|
||||
Some(PreviewKind::Noop) => JobPayload::Noop,
|
||||
_ => JobPayload::Code(RawCode {
|
||||
hash: Some(PREVIEW_IS_CODEBASE_HASH),
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
lock: preview.lock,
|
||||
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
|
||||
concurrency_time_window_s: None, // TODO(gbouv): same as above
|
||||
cache_ttl: None,
|
||||
dedicated_worker: preview.dedicated_worker,
|
||||
custom_concurrency_key: None,
|
||||
}),
|
||||
},
|
||||
JobPayload::Code(RawCode {
|
||||
hash: if is_tar {
|
||||
Some(PREVIEW_IS_TAR_CODEBASE_HASH)
|
||||
} else {
|
||||
Some(PREVIEW_IS_CODEBASE_HASH)
|
||||
},
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
lock: preview.lock,
|
||||
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
|
||||
concurrency_time_window_s: None, // TODO(gbouv): same as above
|
||||
cache_ttl: None,
|
||||
dedicated_worker: preview.dedicated_worker,
|
||||
custom_concurrency_key: None,
|
||||
}),
|
||||
PushArgs::from(&args),
|
||||
authed.display_username(),
|
||||
&authed.email,
|
||||
@@ -3784,7 +3941,7 @@ async fn run_bundle_preview_script(
|
||||
tx = Some(ntx);
|
||||
}
|
||||
if name == "file" {
|
||||
let id = job_id
|
||||
let mut id = job_id
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
@@ -3793,6 +3950,12 @@ async fn run_bundle_preview_script(
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
// tracing::info!("is_tar 2: {is_tar}");
|
||||
|
||||
if is_tar {
|
||||
id = format!("{}.tar", id);
|
||||
}
|
||||
|
||||
uploaded = true;
|
||||
|
||||
if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
@@ -3875,22 +4038,27 @@ async fn run_dependencies_job(
|
||||
let raw_script = req.raw_scripts[0].clone();
|
||||
let script_path = raw_script.script_path;
|
||||
let ehm = HashMap::new();
|
||||
let raw_code = raw_script.raw_code.unwrap_or_else(|| "".to_string());
|
||||
let language = raw_script.language;
|
||||
|
||||
let (args, raw_code) = if let Some(deps) = req.raw_deps {
|
||||
let mut hm = HashMap::new();
|
||||
hm.insert(
|
||||
"raw_deps".to_string(),
|
||||
JsonRawValue::from_string("true".to_string()).unwrap(),
|
||||
);
|
||||
if language == ScriptLang::Bun {
|
||||
let annotation = windmill_common::worker::get_annotation(&raw_code);
|
||||
hm.insert(
|
||||
"npm_mode".to_string(),
|
||||
JsonRawValue::from_string(annotation.npm_mode.to_string()).unwrap(),
|
||||
);
|
||||
}
|
||||
(PushArgs { extra: Some(hm), args: &ehm }, deps)
|
||||
} else {
|
||||
(
|
||||
PushArgs::from(&ehm),
|
||||
raw_script.raw_code.unwrap_or_else(|| "".to_string()),
|
||||
)
|
||||
(PushArgs::from(&ehm), raw_code)
|
||||
};
|
||||
|
||||
let language = raw_script.language;
|
||||
|
||||
let (uuid, tx) = push(
|
||||
&db,
|
||||
PushIsolationLevel::IsolatedRoot(db.clone(), rsmq),
|
||||
@@ -4230,6 +4398,31 @@ pub async fn run_job_by_hash(
|
||||
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
args: PushArgsOwned,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
run_job_by_hash_inner(
|
||||
authed,
|
||||
db,
|
||||
user_db,
|
||||
rsmq,
|
||||
w_id,
|
||||
script_hash,
|
||||
run_query,
|
||||
args,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_job_by_hash_inner(
|
||||
authed: ApiAuthed,
|
||||
db: DB,
|
||||
user_db: UserDB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
w_id: String,
|
||||
script_hash: ScriptHash,
|
||||
run_query: RunJobQuery,
|
||||
args: PushArgsOwned,
|
||||
label_prefix: Option<String>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
check_license_key_valid().await?;
|
||||
@@ -4276,7 +4469,9 @@ pub async fn run_job_by_hash(
|
||||
priority,
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
&label_prefix
|
||||
.map(|x| x + authed.display_username())
|
||||
.unwrap_or_else(|| authed.display_username().to_string()),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
@@ -4402,6 +4597,7 @@ async fn get_job_update(
|
||||
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
|
||||
));
|
||||
}
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
|
||||
Ok(Json(JobUpdate {
|
||||
running: if !running && record.running {
|
||||
Some(true)
|
||||
@@ -4437,6 +4633,7 @@ async fn get_job_update(
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
|
||||
Ok(Json(JobUpdate {
|
||||
running: Some(false),
|
||||
completed: Some(true),
|
||||
@@ -4688,7 +4885,7 @@ async fn get_completed_job<'a>(
|
||||
schedule_path, permissioned_as, flow_status, raw_flow, is_flow_step, language, started_at, is_skipped,
|
||||
raw_lock, email, visible_to_owner, mem_peak, tag, priority, result->'wm_labels' as labels FROM completed_job WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(w_id)
|
||||
.bind(&w_id)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
@@ -4710,6 +4907,9 @@ async fn get_completed_job<'a>(
|
||||
// )
|
||||
// .fetch_optional(db)
|
||||
// .await.ok().flatten().flatten();
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
@@ -4804,6 +5004,8 @@ async fn get_completed_job_result(
|
||||
raw_result.result.map(|x| x.0),
|
||||
);
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(Json(result).into_response())
|
||||
}
|
||||
|
||||
@@ -4845,6 +5047,9 @@ async fn get_completed_job_result_maybe(
|
||||
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
|
||||
|
||||
Ok(Json(CompletedJobResult {
|
||||
started: Some(true),
|
||||
success: Some(res.success),
|
||||
@@ -4891,7 +5096,7 @@ async fn delete_completed_job<'a>(
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let job_o = sqlx::query_as::<_, CompletedJob>(
|
||||
"UPDATE completed_job SET args = null, logs = '', result = null, deleted = true WHERE id = $1 AND workspace_id = $2 \
|
||||
RETURNING *",
|
||||
RETURNING *, null as labels",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(&w_id)
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
*/
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee::ExternalJwks;
|
||||
#[cfg(feature = "embedding")]
|
||||
use crate::embeddings::load_embeddings_db;
|
||||
use crate::oauth2_ee::AllClients;
|
||||
use crate::smtp_server_ee::SmtpServer;
|
||||
use crate::tracing_init::MyOnFailure;
|
||||
use crate::{
|
||||
oauth2_ee::SlackVerifier,
|
||||
@@ -74,6 +77,7 @@ mod schedule;
|
||||
mod scim_ee;
|
||||
mod scripts;
|
||||
mod settings;
|
||||
pub mod smtp_server_ee;
|
||||
mod static_assets;
|
||||
mod stripe_ee;
|
||||
mod tracing_init;
|
||||
@@ -141,15 +145,27 @@ pub async fn add_webhook_allowed_origin(
|
||||
next.run(req).await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tantivy"))]
|
||||
type IndexReader = ();
|
||||
|
||||
#[cfg(not(feature = "tantivy"))]
|
||||
type IndexWriter = ();
|
||||
|
||||
#[cfg(feature = "tantivy")]
|
||||
type IndexReader = windmill_indexer::indexer_ee::IndexReader;
|
||||
#[cfg(feature = "tantivy")]
|
||||
type IndexWriter = windmill_indexer::indexer_ee::IndexWriter;
|
||||
|
||||
pub async fn run_server(
|
||||
db: DB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
index_reader: Option<windmill_indexer::indexer_ee::IndexReader>,
|
||||
index_writer: Option<windmill_indexer::indexer_ee::IndexWriter>,
|
||||
index_reader: Option<IndexReader>,
|
||||
index_writer: Option<IndexWriter>,
|
||||
addr: SocketAddr,
|
||||
mut rx: tokio::sync::broadcast::Receiver<()>,
|
||||
port_tx: tokio::sync::oneshot::Sender<String>,
|
||||
server_mode: bool,
|
||||
base_internal_url: String,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(mut rsmq) = rsmq.clone() {
|
||||
for tag in ALL_TAGS.read().await.iter() {
|
||||
@@ -164,9 +180,13 @@ pub async fn run_server(
|
||||
}
|
||||
let user_db = UserDB::new(db.clone());
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let ext_jwks = ExternalJwks::load().await;
|
||||
let auth_cache = Arc::new(users::AuthCache::new(
|
||||
db.clone(),
|
||||
std::env::var("SUPERADMIN_SECRET").ok(),
|
||||
#[cfg(feature = "enterprise")]
|
||||
ext_jwks,
|
||||
));
|
||||
let argon2 = Arc::new(Argon2::default());
|
||||
|
||||
@@ -177,8 +197,8 @@ pub async fn run_server(
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
.layer(Extension(db.clone()))
|
||||
.layer(Extension(rsmq))
|
||||
.layer(Extension(user_db))
|
||||
.layer(Extension(rsmq.clone()))
|
||||
.layer(Extension(user_db.clone()))
|
||||
.layer(Extension(auth_cache.clone()))
|
||||
.layer(Extension(index_reader))
|
||||
.layer(Extension(index_writer))
|
||||
@@ -197,7 +217,18 @@ pub async fn run_server(
|
||||
|
||||
if server_mode {
|
||||
#[cfg(feature = "embedding")]
|
||||
load_embeddings_db(&db)
|
||||
load_embeddings_db(&db);
|
||||
|
||||
let smtp_server = Arc::new(SmtpServer {
|
||||
db: db.clone(),
|
||||
user_db: user_db,
|
||||
auth_cache: auth_cache.clone(),
|
||||
rsmq: rsmq,
|
||||
base_internal_url: base_internal_url.clone(),
|
||||
});
|
||||
if let Err(err) = smtp_server.start_listener_thread(addr).await {
|
||||
tracing::error!("Error starting SMTP server: {err:#}");
|
||||
}
|
||||
}
|
||||
|
||||
let job_helpers_service = {
|
||||
|
||||
@@ -52,7 +52,7 @@ use windmill_common::{
|
||||
utils::{
|
||||
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
|
||||
},
|
||||
worker::to_raw_value,
|
||||
worker::{get_annotation, to_raw_value},
|
||||
HUB_BASE_URL,
|
||||
};
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
@@ -352,13 +352,16 @@ async fn create_snapshot_script(
|
||||
let mut script_hash = None;
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
if name == "script" {
|
||||
let ns = Some(serde_json::from_slice(&data).map_err(to_anyhow)?);
|
||||
let ns: NewScript = Some(serde_json::from_slice(&data).map_err(to_anyhow)?).unwrap();
|
||||
let is_tar = ns.codebase.as_ref().is_some_and(|x| x.ends_with(".tar"));
|
||||
|
||||
let (new_hash, ntx) = create_script_internal(
|
||||
ns.unwrap(),
|
||||
ns,
|
||||
w_id.clone(),
|
||||
authed.clone(),
|
||||
db.clone(),
|
||||
@@ -367,7 +370,8 @@ async fn create_snapshot_script(
|
||||
webhook.clone(),
|
||||
)
|
||||
.await?;
|
||||
script_hash = Some(new_hash.to_string());
|
||||
let nh = new_hash.to_string();
|
||||
script_hash = Some(if is_tar { format!("{nh}.tar") } else { nh });
|
||||
tx = Some(ntx);
|
||||
}
|
||||
if name == "file" {
|
||||
@@ -567,9 +571,12 @@ async fn create_script_internal<'c>(
|
||||
.as_ref()
|
||||
.map(|v| v.perms.clone())
|
||||
.unwrap_or(json!({}));
|
||||
let lock = if !(ns.language == ScriptLang::Python3
|
||||
let lock = if ns.codebase.is_some() {
|
||||
Some(String::new())
|
||||
} else if !(ns.language == ScriptLang::Python3
|
||||
|| ns.language == ScriptLang::Go
|
||||
|| ns.language == ScriptLang::Bun
|
||||
|| ns.language == ScriptLang::Bunnative
|
||||
|| ns.language == ScriptLang::Deno
|
||||
|| ns.language == ScriptLang::Php)
|
||||
{
|
||||
@@ -578,13 +585,25 @@ async fn create_script_internal<'c>(
|
||||
ns.lock
|
||||
.and_then(|e| if e.is_empty() { None } else { Some(e) })
|
||||
};
|
||||
let needs_lock_gen = lock.is_none();
|
||||
|
||||
let needs_lock_gen = lock.is_none() && codebase.is_none();
|
||||
let envs = ns.envs.as_ref().map(|x| x.as_slice());
|
||||
let envs = if ns.envs.is_none() || ns.envs.as_ref().unwrap().is_empty() {
|
||||
None
|
||||
} else {
|
||||
envs
|
||||
};
|
||||
|
||||
let lang = if &ns.language == &ScriptLang::Bun || &ns.language == &ScriptLang::Bunnative {
|
||||
let anns = get_annotation(&ns.content);
|
||||
if anns.native_mode {
|
||||
ScriptLang::Bunnative
|
||||
} else {
|
||||
ScriptLang::Bun
|
||||
}
|
||||
} else {
|
||||
ns.language.clone()
|
||||
};
|
||||
sqlx::query!(
|
||||
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \
|
||||
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
|
||||
@@ -604,7 +623,7 @@ async fn create_script_internal<'c>(
|
||||
ns.is_template.unwrap_or(false),
|
||||
extra_perms,
|
||||
lock,
|
||||
ns.language.clone() as ScriptLang,
|
||||
lang as ScriptLang,
|
||||
ns.kind.unwrap_or(ScriptKind::Script) as ScriptKind,
|
||||
ns.tag,
|
||||
ns.draft_only,
|
||||
@@ -798,7 +817,8 @@ pub async fn get_full_hub_script_by_path(
|
||||
Extension(db): Extension<DB>,
|
||||
) -> JsonResult<HubScript> {
|
||||
Ok(Json(
|
||||
windmill_common::scripts::get_full_hub_script_by_path(path, &HTTP_CLIENT, &db).await?,
|
||||
windmill_common::scripts::get_full_hub_script_by_path(path, &HTTP_CLIENT, Some(&db))
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,16 @@ use axum::{
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::extract::Query;
|
||||
|
||||
use serde::Deserialize;
|
||||
use windmill_common::{
|
||||
error::{self, JsonResult, Result},
|
||||
global_settings::{AUTOMATE_USERNAME_CREATION_SETTING, ENV_SETTINGS, HUB_BASE_URL_SETTING},
|
||||
global_settings::{
|
||||
AUTOMATE_USERNAME_CREATION_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
|
||||
HUB_BASE_URL_SETTING,
|
||||
},
|
||||
server::Smtp,
|
||||
utils::send_email,
|
||||
};
|
||||
@@ -116,14 +122,17 @@ pub async fn test_s3_bucket(
|
||||
let client = build_object_store_from_settings(test_s3_bucket).await?;
|
||||
|
||||
let mut list = client.list(Some(&object_store::path::Path::from("".to_string())));
|
||||
let first_file = list
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
error::Error::InternalErr("Failed to list files in blob storage".to_string())
|
||||
})?
|
||||
.map_err(|e| anyhow::anyhow!("error listing bucket: {e:#}"))?;
|
||||
tracing::info!("Listed files: {:?}", first_file);
|
||||
let first_file = list.next().await;
|
||||
if first_file.is_some() {
|
||||
if let Err(e) = first_file.as_ref().unwrap() {
|
||||
tracing::error!("error listing bucket: {e:#}");
|
||||
error::Error::InternalErr(format!("Failed to list files in blob storage: {e:#}"));
|
||||
}
|
||||
tracing::info!("Listed files: {:?}", first_file.unwrap());
|
||||
} else {
|
||||
tracing::info!("No files in blob storage");
|
||||
}
|
||||
|
||||
let path = object_store::path::Path::from(format!(
|
||||
"/test-s3-bucket-{uuid}",
|
||||
uuid = uuid::Uuid::new_v4()
|
||||
@@ -231,12 +240,12 @@ pub async fn set_global_setting_internal(
|
||||
}
|
||||
v => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
|
||||
key,
|
||||
v
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
|
||||
key,
|
||||
v
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
tracing::info!("Set global setting {} to {}", key, v);
|
||||
}
|
||||
};
|
||||
@@ -253,6 +262,7 @@ pub async fn get_global_setting(
|
||||
&& !key.starts_with("default_recovery_handler_")
|
||||
&& key != AUTOMATE_USERNAME_CREATION_SETTING
|
||||
&& key != HUB_BASE_URL_SETTING
|
||||
&& key != EMAIL_DOMAIN_SETTING
|
||||
{
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
}
|
||||
@@ -332,6 +342,12 @@ pub async fn get_latest_key_renewal_attempt(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct LicenseQuery {
|
||||
license_key: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub async fn renew_license_key() -> Result<String> {
|
||||
return Err(error::Error::BadRequest(
|
||||
@@ -340,15 +356,23 @@ pub async fn renew_license_key() -> Result<String> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn renew_license_key(Extension(db): Extension<DB>, authed: ApiAuthed) -> Result<String> {
|
||||
pub async fn renew_license_key(
|
||||
Extension(db): Extension<DB>,
|
||||
Query(LicenseQuery { license_key }): Query<LicenseQuery>,
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
windmill_common::stats_ee::send_stats(&"manual".to_string(), &HTTP_CLIENT, &db).await?;
|
||||
let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db).await;
|
||||
let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db, license_key).await;
|
||||
|
||||
if result != "success" {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"Failed to renew license key: {}",
|
||||
result
|
||||
if result == "Unauthorized" {
|
||||
"Invalid key".to_string()
|
||||
} else {
|
||||
result
|
||||
}
|
||||
)));
|
||||
} else {
|
||||
return Ok("Renewed license key".to_string());
|
||||
@@ -363,8 +387,11 @@ pub async fn create_customer_portal_session() -> Result<String> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn create_customer_portal_session() -> Result<String> {
|
||||
let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT).await?;
|
||||
pub async fn create_customer_portal_session(
|
||||
Query(LicenseQuery { license_key }): Query<LicenseQuery>,
|
||||
) -> Result<String> {
|
||||
let url =
|
||||
windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT, license_key).await?;
|
||||
|
||||
return Ok(url);
|
||||
}
|
||||
|
||||
17
backend/windmill-api/src/smtp_server_ee.rs
Normal file
17
backend/windmill-api/src/smtp_server_ee.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::{db::DB, users::AuthCache};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use windmill_common::db::UserDB;
|
||||
|
||||
pub struct SmtpServer {
|
||||
pub auth_cache: Arc<AuthCache>,
|
||||
pub db: DB,
|
||||
pub user_db: UserDB,
|
||||
pub rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
pub base_internal_url: String,
|
||||
}
|
||||
|
||||
impl SmtpServer {
|
||||
pub async fn start_listener_thread(self: Arc<Self>, _addr: SocketAddr) -> anyhow::Result<()> {
|
||||
Err(anyhow::anyhow!("Implementation not open source"))
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ use std::sync::Arc;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee::ExternalJwks;
|
||||
use crate::oauth2_ee::{check_nb_of_user, InstanceEvent};
|
||||
use crate::utils::{
|
||||
generate_instance_wide_unique_username, get_and_delete_pending_username_or_generate,
|
||||
@@ -39,6 +41,8 @@ use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use tokio::sync::RwLock;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
use tracing::{Instrument, Span};
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
@@ -145,11 +149,23 @@ pub struct AuthCache {
|
||||
cache: Cache<(String, String), ExpiringAuthCache>,
|
||||
db: DB,
|
||||
superadmin_secret: Option<String>,
|
||||
#[cfg(feature = "enterprise")]
|
||||
ext_jwks: Option<Arc<RwLock<ExternalJwks>>>,
|
||||
}
|
||||
|
||||
impl AuthCache {
|
||||
pub fn new(db: DB, superadmin_secret: Option<String>) -> Self {
|
||||
AuthCache { cache: Cache::new(300), db, superadmin_secret }
|
||||
pub fn new(
|
||||
db: DB,
|
||||
superadmin_secret: Option<String>,
|
||||
#[cfg(feature = "enterprise")] ext_jwks: Option<Arc<RwLock<ExternalJwks>>>,
|
||||
) -> Self {
|
||||
AuthCache {
|
||||
cache: Cache::new(300),
|
||||
db,
|
||||
superadmin_secret,
|
||||
#[cfg(feature = "enterprise")]
|
||||
ext_jwks,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn invalidate(&self, w_id: &str, token: String) {
|
||||
@@ -168,9 +184,19 @@ impl AuthCache {
|
||||
}
|
||||
#[cfg(feature = "enterprise")]
|
||||
_ if token.starts_with("jwt_ext_") => {
|
||||
let authed_and_exp =
|
||||
crate::ee::jwt_ext_auth(w_id.as_ref(), token.trim_start_matches("jwt_ext_"))
|
||||
.await;
|
||||
let authed_and_exp = match crate::ee::jwt_ext_auth(
|
||||
w_id.as_ref(),
|
||||
token.trim_start_matches("jwt_ext_"),
|
||||
self.ext_jwks.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
tracing::error!("JWT_EXT auth error: {:?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((authed, exp)) = authed_and_exp.clone() {
|
||||
self.cache.insert(
|
||||
|
||||
@@ -25,7 +25,7 @@ use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
utils::{not_found_if_none, StripPath},
|
||||
utils::{not_found_if_none, paginate, Pagination, StripPath},
|
||||
variables::{
|
||||
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
|
||||
},
|
||||
@@ -92,7 +92,10 @@ async fn list_variables(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(lq): Query<ListVariableQuery>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
) -> JsonResult<Vec<ListableVariable>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let rows = sqlx::query_as::<_, ListableVariable>(
|
||||
@@ -100,17 +103,22 @@ async fn list_variables(
|
||||
is_secret, variable.description, variable.extra_perms, account, is_oauth, (now() > account.expires_at) as is_expired,
|
||||
account.refresh_error,
|
||||
resource.path IS NOT NULL as is_linked,
|
||||
account.refresh_token != '' as is_refreshed
|
||||
account.refresh_token != '' as is_refreshed,
|
||||
variable.expires_at
|
||||
from variable
|
||||
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1
|
||||
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1
|
||||
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%'
|
||||
AND variable.path LIKE $3 || '%'
|
||||
ORDER BY path",
|
||||
ORDER BY path
|
||||
LIMIT $4 OFFSET $5
|
||||
",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.bind(&authed.username)
|
||||
.bind(&lq.path_start.unwrap_or_default())
|
||||
.bind(per_page as i32)
|
||||
.bind(offset as i32)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -310,8 +318,8 @@ async fn create_variable(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO variable
|
||||
(workspace_id, path, value, is_secret, description, account, is_oauth)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
(workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
&w_id,
|
||||
variable.path,
|
||||
value,
|
||||
@@ -319,6 +327,7 @@ async fn create_variable(
|
||||
variable.description,
|
||||
variable.account,
|
||||
variable.is_oauth.unwrap_or(false),
|
||||
variable.expires_at
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -42,7 +42,7 @@ use windmill_common::schedule::Schedule;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::variables::build_crypt;
|
||||
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
|
||||
use windmill_common::workspaces::WorkspaceGitSyncSettings;
|
||||
use windmill_common::workspaces::{WorkspaceDeploymentUISettings, WorkspaceGitSyncSettings};
|
||||
use windmill_common::{
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
flows::Flow,
|
||||
@@ -97,6 +97,7 @@ pub fn workspaced_service() -> Router {
|
||||
post(edit_large_file_storage_config),
|
||||
)
|
||||
.route("/edit_git_sync_config", post(edit_git_sync_config))
|
||||
.route("/edit_deploy_ui_config", post(edit_deploy_ui_config))
|
||||
.route("/edit_default_app", post(edit_default_app))
|
||||
.route("/default_app", get(get_default_app))
|
||||
.route(
|
||||
@@ -167,8 +168,9 @@ pub struct WorkspaceSettings {
|
||||
pub error_handler: Option<String>,
|
||||
pub error_handler_extra_args: Option<serde_json::Value>,
|
||||
pub error_handler_muted_on_cancel: Option<bool>,
|
||||
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
|
||||
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
|
||||
pub deploy_ui: Option<serde_json::Value>, // effectively: WorkspaceDeploymentUISettings
|
||||
pub default_app: Option<String>,
|
||||
pub automatic_billing: bool,
|
||||
pub default_scripts: Option<serde_json::Value>,
|
||||
@@ -1052,6 +1054,74 @@ async fn edit_git_sync_config(
|
||||
Ok(format!("Edit git sync config for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditDeployUIConfig {
|
||||
deploy_ui_settings: Option<WorkspaceDeploymentUISettings>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
async fn edit_deploy_ui_config(
|
||||
_authed: ApiAuthed,
|
||||
Extension(_db): Extension<DB>,
|
||||
Path(_w_id): Path<String>,
|
||||
Json(_new_config): Json<EditDeployUIConfig>,
|
||||
) -> Result<String> {
|
||||
return Err(Error::BadRequest(
|
||||
"Deployment UI is only available on Windmill Enterprise Edition".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn edit_deploy_ui_config(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, .. }: ApiAuthed,
|
||||
Json(new_config): Json<EditDeployUIConfig>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let args_for_audit = format!("{:?}", new_config.deploy_ui_settings);
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"workspaces.edit_deploy_ui_config",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([("deployment_ui_settings", args_for_audit.as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(deploy_ui_settings) = new_config.deploy_ui_settings {
|
||||
let serialized_config = serde_json::to_value::<WorkspaceDeploymentUISettings>(deploy_ui_settings)
|
||||
.map_err(|err| Error::InternalErr(err.to_string()))?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2",
|
||||
serialized_config,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1",
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit deployment UI config for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditDefaultApp {
|
||||
pub default_app_path: Option<String>,
|
||||
@@ -2498,7 +2568,7 @@ async fn tarball_workspace(
|
||||
ScriptLang::Mssql => "ms.sql",
|
||||
ScriptLang::Graphql => "gql",
|
||||
ScriptLang::Nativets => "fetch.ts",
|
||||
ScriptLang::Bun => {
|
||||
ScriptLang::Bun | ScriptLang::Bunnative => {
|
||||
if default_ts.as_ref().is_some_and(|x| x == "bun") {
|
||||
"ts"
|
||||
} else {
|
||||
@@ -2599,9 +2669,9 @@ async fn tarball_workspace(
|
||||
if !skip_variables.unwrap_or(false) {
|
||||
let variables =
|
||||
sqlx::query_as::<_, ExportableListableVariable>(if !skip_secrets.unwrap_or(false) {
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND path NOT LIKE 'u/%/secret_arg/%'"
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND expires_at IS NULL"
|
||||
} else {
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND path NOT LIKE 'u/%/secret_arg/%'"
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND expires_at IS NULL"
|
||||
})
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
|
||||
@@ -11,7 +11,7 @@ jemalloc = ["dep:tikv-jemalloc-ctl"]
|
||||
prometheus = ["dep:prometheus"]
|
||||
flamegraph = ["dep:tracing-flame"]
|
||||
loki = ["dep:tracing-loki"]
|
||||
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts", "dep:bytes"]
|
||||
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"]
|
||||
|
||||
[lib]
|
||||
name = "windmill_common"
|
||||
@@ -48,8 +48,10 @@ prometheus = { workspace = true, optional = true }
|
||||
aws-config = { workspace = true, optional = true }
|
||||
aws-sdk-sts = { workspace = true, optional = true }
|
||||
indexmap.workspace = true
|
||||
bytes = { workspace = true, optional = true }
|
||||
bytes.workspace = true
|
||||
mail-send.workspace = true
|
||||
futures-core.workspace = true
|
||||
async-stream.workspace = true
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemalloc-ctl = { optional = true, workspace = true }
|
||||
|
||||
@@ -34,7 +34,11 @@ pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::d
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn renew_license_key(_http_client: &reqwest::Client, _db: &crate::db::DB) -> String {
|
||||
pub async fn renew_license_key(
|
||||
_http_client: &reqwest::Client,
|
||||
_db: &crate::db::DB,
|
||||
_key: Option<String>,
|
||||
) -> String {
|
||||
// Implementation is not open source
|
||||
"".to_string()
|
||||
}
|
||||
@@ -42,6 +46,7 @@ pub async fn renew_license_key(_http_client: &reqwest::Client, _db: &crate::db::
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn create_customer_portal_session(
|
||||
_http_client: &reqwest::Client,
|
||||
_key: Option<String>,
|
||||
) -> error::Result<String> {
|
||||
// Implementation is not open source
|
||||
Ok("".to_string())
|
||||
|
||||
@@ -38,22 +38,26 @@ pub struct Flow {
|
||||
pub dedicated_worker: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tag: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(skip_serializing_if = "is_none_or_false")]
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(skip_serializing_if = "is_none_or_false")]
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
}
|
||||
|
||||
fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
b.is_none() || !b.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
pub struct ListableFlow {
|
||||
pub workspace_id: String,
|
||||
pub path: String,
|
||||
pub summary: String,
|
||||
pub description: String,
|
||||
pub edited_by: String,
|
||||
pub edited_at: chrono::DateTime<chrono::Utc>,
|
||||
pub edited_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub archived: bool,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub starred: bool,
|
||||
@@ -209,6 +213,12 @@ pub struct Suspend {
|
||||
pub self_approval_disabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hide_cancel: Option<bool>,
|
||||
#[serde(skip_serializing_if = "false_or_empty")]
|
||||
pub continue_on_disapprove_timeout: Option<bool>,
|
||||
}
|
||||
|
||||
fn false_or_empty(v: &Option<bool>) -> bool {
|
||||
v.is_none() || v.as_ref().is_some_and(|x| !x)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
@@ -406,7 +416,7 @@ pub enum FlowModuleValue {
|
||||
lock: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(skip_serializing_if = "is_none_or_empty")]
|
||||
tag: Option<String>,
|
||||
language: ScriptLang,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -419,6 +429,10 @@ pub enum FlowModuleValue {
|
||||
Identity,
|
||||
}
|
||||
|
||||
fn is_none_or_empty(expr: &Option<String>) -> bool {
|
||||
expr.is_none() || expr.as_ref().unwrap().is_empty()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UntaggedFlowModuleValue {
|
||||
#[serde(rename = "type")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub const CUSTOM_TAGS_SETTING: &str = "custom_tags";
|
||||
pub const DEFAULT_TAGS_PER_WORKSPACE_SETTING: &str = "default_tags_per_workspace";
|
||||
pub const DEFAULT_TAGS_WORKSPACES_SETTING: &str = "default_tags_workspaces";
|
||||
pub const BASE_URL_SETTING: &str = "base_url";
|
||||
pub const OAUTH_SETTING: &str = "oauths";
|
||||
pub const RETENTION_PERIOD_SECS_SETTING: &str = "retention_period_secs";
|
||||
@@ -27,8 +28,9 @@ pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
|
||||
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
|
||||
pub const DEV_INSTANCE_SETTING: &str = "dev_instance";
|
||||
pub const JWT_SECRET_SETTING: &str = "jwt_secret";
|
||||
pub const EMAIL_DOMAIN_SETTING: &str = "email_domain";
|
||||
|
||||
pub const ENV_SETTINGS: [&str; 50] = [
|
||||
pub const ENV_SETTINGS: [&str; 51] = [
|
||||
"DISABLE_NSJAIL",
|
||||
"MODE",
|
||||
"NUM_WORKERS",
|
||||
@@ -79,4 +81,5 @@ pub const ENV_SETTINGS: [&str; 50] = [
|
||||
"MAX_WAIT_FOR_SIGTERM",
|
||||
"WORKER_GROUP",
|
||||
"SAML_METADATA",
|
||||
"INSTANCE_IS_DEV",
|
||||
];
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::{types::Json, Pool, Postgres, Transaction};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const ENTRYPOINT_OVERRIDE: &str = "_ENTRYPOINT_OVERRIDE";
|
||||
|
||||
use crate::{
|
||||
error::{self, Error},
|
||||
error::{self, to_anyhow, Error},
|
||||
flow_status::{FlowStatus, RestartedFrom},
|
||||
flows::{FlowValue, Retry},
|
||||
get_latest_deployed_hash_for_path,
|
||||
scripts::{ScriptHash, ScriptLang},
|
||||
worker::to_raw_value,
|
||||
worker::{to_raw_value, TMP_DIR},
|
||||
};
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
@@ -580,3 +583,75 @@ pub fn format_completed_job_result(mut cj: CompletedJob) -> CompletedJobWithForm
|
||||
cj.result = None; // very important to avoid sending the result twice
|
||||
CompletedJobWithFormattedResult { cj, result: Some(sql_result) }
|
||||
}
|
||||
|
||||
pub async fn get_logs_from_disk(
|
||||
log_offset: i32,
|
||||
logs: &str,
|
||||
log_file_index: &Option<Vec<String>>,
|
||||
) -> Option<impl Stream<Item = Result<Bytes, anyhow::Error>>> {
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
for file_p in &file_index {
|
||||
if !tokio::fs::metadata(format!("{TMP_DIR}/{file_p}"))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let logs = logs.to_string();
|
||||
let stream = async_stream::stream! {
|
||||
for file_p in file_index.clone() {
|
||||
let mut file = tokio::fs::File::open(format!("{TMP_DIR}/{file_p}")).await.map_err(to_anyhow)?;
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await.map_err(to_anyhow)?;
|
||||
yield Ok(bytes::Bytes::from(buffer)) as anyhow::Result<bytes::Bytes>;
|
||||
}
|
||||
|
||||
yield Ok(bytes::Bytes::from(logs))
|
||||
};
|
||||
return Some(stream);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
pub async fn get_logs_from_store(
|
||||
log_offset: i32,
|
||||
logs: &str,
|
||||
log_file_index: &Option<Vec<String>>,
|
||||
) -> Option<impl Stream<Item = Result<Bytes, object_store::Error>>> {
|
||||
use crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS;
|
||||
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
tracing::debug!("Getting logs from store: {file_index:?}");
|
||||
if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
|
||||
tracing::debug!("object store client present, streaming from there");
|
||||
|
||||
let logs = logs.to_string();
|
||||
let stream = async_stream::stream! {
|
||||
for file_p in file_index.clone() {
|
||||
let file_p_2 = file_p.clone();
|
||||
let file = os.get(&object_store::path::Path::from(file_p)).await;
|
||||
if let Ok(file) = file {
|
||||
if let Ok(bytes) = file.bytes().await {
|
||||
yield Ok(bytes::Bytes::from(bytes)) as object_store::Result<bytes::Bytes>;
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("error getting file from store: {file_p_2}: {}", file.err().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
yield Ok(bytes::Bytes::from(logs))
|
||||
};
|
||||
return Some(stream);
|
||||
} else {
|
||||
tracing::debug!("object store client not present, cannot stream logs from store");
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user